Merge pull request #3783 from epage/refactor

refactor(derive): Prepare for action support
This commit is contained in:
Ed Page 2022-06-03 09:56:56 -05:00 committed by GitHub
commit e8ad62b784
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 5395 additions and 81 deletions

View file

@ -64,10 +64,10 @@ impl Attrs {
res.push_attrs(attrs);
res.push_doc_comment(attrs, "about");
if let Some(parser) = res.value_parser.as_ref() {
if let Some(value_parser) = res.value_parser.as_ref() {
abort!(
parser.span(),
"`value_parse` attribute is only allowed on fields"
value_parser.span(),
"`value_parser` attribute is only allowed on fields"
);
}
if let Some(parser) = res.parser.as_ref() {
@ -104,10 +104,10 @@ impl Attrs {
match &*res.kind {
Kind::Flatten => {
if let Some(parser) = res.value_parser.as_ref() {
if let Some(value_parser) = res.value_parser.as_ref() {
abort!(
parser.span(),
"`value_parse` attribute is only allowed flattened entry"
value_parser.span(),
"`value_parser` attribute is not allowed for flattened entry"
);
}
if let Some(parser) = res.parser.as_ref() {
@ -130,10 +130,10 @@ impl Attrs {
Kind::ExternalSubcommand => (),
Kind::Subcommand(_) => {
if let Some(parser) = res.value_parser.as_ref() {
if let Some(value_parser) = res.value_parser.as_ref() {
abort!(
parser.span(),
"`value_parse` attribute is only allowed for subcommand"
value_parser.span(),
"`value_parser` attribute is not allowed for subcommand"
);
}
if let Some(parser) = res.parser.as_ref() {
@ -204,10 +204,10 @@ impl Attrs {
res.push_attrs(&variant.attrs);
res.push_doc_comment(&variant.attrs, "help");
if let Some(parser) = res.value_parser.as_ref() {
if let Some(value_parser) = res.value_parser.as_ref() {
abort!(
parser.span(),
"`value_parse` attribute is only allowed on fields"
value_parser.span(),
"`value_parser` attribute is only allowed on fields"
);
}
if let Some(parser) = res.parser.as_ref() {
@ -244,10 +244,10 @@ impl Attrs {
match &*res.kind {
Kind::Flatten => {
if let Some(parser) = res.value_parser.as_ref() {
if let Some(value_parser) = res.value_parser.as_ref() {
abort!(
parser.span(),
"`value_parse` attribute is not allowed for flattened entry"
value_parser.span(),
"`value_parser` attribute is not allowed for flattened entry"
);
}
if let Some(parser) = res.parser.as_ref() {
@ -274,10 +274,10 @@ impl Attrs {
}
Kind::Subcommand(_) => {
if let Some(parser) = res.value_parser.as_ref() {
if let Some(value_parser) = res.value_parser.as_ref() {
abort!(
parser.span(),
"`value_parse` attribute is not allowed for subcommand"
value_parser.span(),
"`value_parser` attribute is not allowed for subcommand"
);
}
if let Some(parser) = res.parser.as_ref() {
@ -330,7 +330,7 @@ impl Attrs {
if let Some(value_parser) = res.value_parser.as_ref() {
abort!(
value_parser.span(),
"`value_parse` attribute conflicts with `parse` attribute"
"`value_parser` attribute conflicts with `parse` attribute"
);
}
match *ty {
@ -721,7 +721,7 @@ impl Attrs {
.unwrap_or_else(|| self.parser(field_type).value_parser())
}
pub fn custom_value_parser(&self) -> bool {
pub fn ignore_parser(&self) -> bool {
self.value_parser.is_some()
}
@ -958,7 +958,7 @@ impl Parser {
),
ParserKind::FromFlag => Method::new(
func,
quote_spanned! { self.kind.span()=> clap::ValueParser::bool()},
quote_spanned! { self.kind.span()=> clap::builder::ValueParser::bool()},
),
}
}

View file

@ -244,14 +244,14 @@ pub fn gen_augment(
let convert_type = inner_type(&field.ty);
let parser = attrs.parser(&field.ty);
let occurrences = *parser.kind == ParserKind::FromOccurrences;
let flag = *parser.kind == ParserKind::FromFlag;
let value_parser = attrs.value_parser(&field.ty);
let func = &parser.func;
let mut occurrences = false;
let mut flag = false;
let validator = match *parser.kind {
_ if attrs.custom_value_parser() || attrs.is_enum() => quote!(),
_ if attrs.ignore_parser() || attrs.is_enum() => quote!(),
ParserKind::TryFromStr => quote_spanned! { func.span()=>
.validator(|s| {
#func(s)
@ -261,14 +261,19 @@ pub fn gen_augment(
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
.validator_os(|s| #func(s).map(|_: #convert_type| ()))
},
ParserKind::FromStr
| ParserKind::FromOsStr
| ParserKind::FromFlag
| ParserKind::FromOccurrences => quote!(),
ParserKind::FromStr | ParserKind::FromOsStr => quote!(),
ParserKind::FromFlag => {
flag = true;
quote!()
}
ParserKind::FromOccurrences => {
occurrences = true;
quote!()
}
};
let value_name = attrs.value_name();
let possible_values = if attrs.is_enum() && !attrs.custom_value_parser() {
let possible_values = if attrs.is_enum() && !attrs.ignore_parser() {
gen_arg_enum_possible_values(convert_type)
} else {
quote!()
@ -524,25 +529,33 @@ fn gen_parsers(
let span = parser.kind.span();
let convert_type = inner_type(&field.ty);
let id = attrs.id();
let mut flag = false;
let mut occurrences = false;
let (get_one, get_many, deref, mut parse) = match *parser.kind {
FromOccurrences => (
quote_spanned!(span=> occurrences_of),
quote!(),
quote!(|s| ::std::ops::Deref::deref(s)),
func.clone(),
),
FromFlag => (
quote!(),
quote!(),
quote!(|s| ::std::ops::Deref::deref(s)),
func.clone(),
),
_ if attrs.custom_value_parser() => (
_ if attrs.ignore_parser() => (
quote_spanned!(span=> remove_one::<#convert_type>),
quote_spanned!(span=> remove_many::<#convert_type>),
quote!(|s| s),
quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(s)),
),
FromOccurrences => {
occurrences = true;
(
quote_spanned!(span=> occurrences_of),
quote!(),
quote!(|s| ::std::ops::Deref::deref(s)),
func.clone(),
)
}
FromFlag => {
flag = true;
(
quote!(),
quote!(),
quote!(|s| ::std::ops::Deref::deref(s)),
func.clone(),
)
}
FromStr => (
quote_spanned!(span=> get_one::<String>),
quote_spanned!(span=> get_many::<String>),
@ -568,7 +581,7 @@ fn gen_parsers(
quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))),
),
};
if attrs.is_enum() && !attrs.custom_value_parser() {
if attrs.is_enum() && !attrs.ignore_parser() {
let ci = attrs.ignore_case();
parse = quote_spanned! { convert_type.span()=>
@ -576,8 +589,6 @@ fn gen_parsers(
}
}
let flag = *parser.kind == ParserKind::FromFlag;
let occurrences = *parser.kind == ParserKind::FromOccurrences;
// Give this identifier the same hygiene
// as the `arg_matches` parameter definition. This
// allows us to refer to `arg_matches` within a `quote_spanned` block

View file

@ -26,7 +26,7 @@ enum Commands {
/// does testing things
Test {
/// lists test values
#[clap(short, long, value_parser)]
#[clap(short, long)]
list: bool,
},
}

View file

@ -3,7 +3,7 @@ use clap::Parser;
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
#[clap(short, long, value_parser)]
#[clap(short, long)]
verbose: bool,
}

View file

@ -18,11 +18,6 @@ pub enum MatchesError {
UnknownArgument {
// Missing `id` but blocked on a public id type which will hopefully come with `unstable-v4`
},
/// Present argument must have one value
#[non_exhaustive]
ExpectedOne {
// Missing `id` but blocked on a public id type which will hopefully come with `unstable-v4`
},
}
impl MatchesError {
@ -56,9 +51,6 @@ impl std::fmt::Display for MatchesError {
Self::UnknownArgument {} => {
writeln!(f, "Unknown argument or group id. Make sure you are using the argument id and not the short or long flags")
}
Self::ExpectedOne {} => {
writeln!(f, "Present argument must have one value. Make sure you are using the correct lookup (`get_one` vs `get_many`)")
}
}
}
}

View file

@ -1063,11 +1063,8 @@ impl ArgMatches {
) -> Result<Option<&T>, MatchesError> {
let id = Id::from(name);
let arg = self.try_get_arg_t::<T>(&id)?;
let value = match arg.map(|a| a.first()) {
Some(Some(value)) => value,
Some(None) => {
return Err(MatchesError::ExpectedOne {});
}
let value = match arg.and_then(|a| a.first()) {
Some(value) => value,
None => {
return Ok(None);
}

View file

@ -38,10 +38,7 @@ fn env_bool_literal() {
let m = r.unwrap();
assert!(m.is_present("present"));
assert_eq!(m.occurrences_of("present"), 0);
assert!(matches!(
m.try_get_one::<String>("present").unwrap_err(),
clap::parser::MatchesError::ExpectedOne { .. }
));
assert_eq!(m.get_one::<String>("present").map(|v| v.as_str()), None);
assert!(!m.is_present("negated"));
assert!(!m.is_present("absent"));
}

View file

@ -111,10 +111,7 @@ fn group_single_flag() {
let m = res.unwrap();
assert!(m.is_present("grp"));
assert!(matches!(
m.try_get_one::<String>("grp").unwrap_err(),
clap::parser::MatchesError::ExpectedOne { .. }
));
assert!(m.get_one::<String>("grp").map(|v| v.as_str()).is_none());
}
#[test]

View file

@ -49,10 +49,7 @@ fn multiple_args_and_final_arg_without_value() {
Some("file")
);
assert!(m.is_present("f"));
assert!(matches!(
m.try_get_one::<String>("stuff").unwrap_err(),
clap::parser::MatchesError::ExpectedOne { .. }
));
assert_eq!(m.get_one::<String>("stuff").map(|v| v.as_str()), None);
}
#[test]
@ -79,10 +76,7 @@ fn multiple_args_and_intermittent_arg_without_value() {
Some("file")
);
assert!(m.is_present("f"));
assert!(matches!(
m.try_get_one::<String>("stuff").unwrap_err(),
clap::parser::MatchesError::ExpectedOne { .. }
));
assert_eq!(m.get_one::<String>("stuff").map(|v| v.as_str()), None);
}
#[test]
@ -124,10 +118,7 @@ fn subcommand() {
sub_m.is_present("test"),
"expected subcommand to be present due to partial parsing"
);
assert!(matches!(
sub_m.try_get_one::<String>("test").unwrap_err(),
clap::parser::MatchesError::ExpectedOne { .. }
));
assert_eq!(sub_m.get_one::<String>("test").map(|v| v.as_str()), None);
assert_eq!(
sub_m.get_one::<String>("stuff").map(|v| v.as_str()),
Some("some other val")

View file

@ -0,0 +1,97 @@
use clap::CommandFactory;
use clap::Parser;
#[test]
fn app_name_in_short_help_from_struct() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
struct MyApp {}
let mut help = Vec::new();
MyApp::command().write_help(&mut help).unwrap();
let help = String::from_utf8(help).unwrap();
assert!(help.contains("my-cmd"));
}
#[test]
fn app_name_in_long_help_from_struct() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
struct MyApp {}
let mut help = Vec::new();
MyApp::command().write_long_help(&mut help).unwrap();
let help = String::from_utf8(help).unwrap();
assert!(help.contains("my-cmd"));
}
#[test]
fn app_name_in_short_help_from_enum() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
enum MyApp {}
let mut help = Vec::new();
MyApp::command().write_help(&mut help).unwrap();
let help = String::from_utf8(help).unwrap();
assert!(help.contains("my-cmd"));
}
#[test]
fn app_name_in_long_help_from_enum() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
enum MyApp {}
let mut help = Vec::new();
MyApp::command().write_long_help(&mut help).unwrap();
let help = String::from_utf8(help).unwrap();
assert!(help.contains("my-cmd"));
}
#[test]
fn app_name_in_short_version_from_struct() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
struct MyApp {}
let version = MyApp::command().render_version();
assert!(version.contains("my-cmd"));
}
#[test]
fn app_name_in_long_version_from_struct() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
struct MyApp {}
let version = MyApp::command().render_long_version();
assert!(version.contains("my-cmd"));
}
#[test]
fn app_name_in_short_version_from_enum() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
enum MyApp {}
let version = MyApp::command().render_version();
assert!(version.contains("my-cmd"));
}
#[test]
fn app_name_in_long_version_from_enum() {
#[derive(Parser)]
#[clap(name = "my-cmd")]
enum MyApp {}
let version = MyApp::command().render_long_version();
assert!(version.contains("my-cmd"));
}

View file

@ -0,0 +1,526 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Parser;
#[test]
fn basic() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum)]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::Foo
},
Opt::try_parse_from(&["", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::Bar
},
Opt::try_parse_from(&["", "bar"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "fOo"]).is_err());
}
#[test]
fn default_value() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
impl Default for ArgChoice {
fn default() -> Self {
Self::Bar
}
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, default_value_t)]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::Foo
},
Opt::try_parse_from(&["", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::Bar
},
Opt::try_parse_from(&["", "bar"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::Bar
},
Opt::try_parse_from(&[""]).unwrap()
);
}
#[test]
fn multi_word_is_renamed_kebab() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
#[allow(non_camel_case_types)]
enum ArgChoice {
FooBar,
BAR_BAZ,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum)]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::FooBar
},
Opt::try_parse_from(&["", "foo-bar"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::BAR_BAZ
},
Opt::try_parse_from(&["", "bar-baz"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
}
#[test]
fn variant_with_defined_casing() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
#[clap(rename_all = "screaming_snake")]
FooBar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum)]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::FooBar
},
Opt::try_parse_from(&["", "FOO_BAR"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
}
#[test]
fn casing_is_propagated_from_parent() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
#[clap(rename_all = "screaming_snake")]
enum ArgChoice {
FooBar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum)]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::FooBar
},
Opt::try_parse_from(&["", "FOO_BAR"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
}
#[test]
fn casing_propagation_is_overridden() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
#[clap(rename_all = "screaming_snake")]
enum ArgChoice {
#[clap(rename_all = "camel")]
FooBar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum)]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::FooBar
},
Opt::try_parse_from(&["", "fooBar"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
assert!(Opt::try_parse_from(&["", "FOO_BAR"]).is_err());
}
#[test]
fn ignore_case() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, ignore_case(true))]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::Foo
},
Opt::try_parse_from(&["", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::Foo
},
Opt::try_parse_from(&["", "fOo"]).unwrap()
);
}
#[test]
fn ignore_case_set_to_false() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, ignore_case(false))]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::Foo
},
Opt::try_parse_from(&["", "foo"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "fOo"]).is_err());
}
#[test]
fn alias() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
#[clap(alias = "TOTP")]
Totp,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, ignore_case(false))]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::Totp
},
Opt::try_parse_from(&["", "totp"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::Totp
},
Opt::try_parse_from(&["", "TOTP"]).unwrap()
);
}
#[test]
fn multiple_alias() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
#[clap(alias = "TOTP", alias = "t")]
Totp,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, ignore_case(false))]
arg: ArgChoice,
}
assert_eq!(
Opt {
arg: ArgChoice::Totp
},
Opt::try_parse_from(&["", "totp"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::Totp
},
Opt::try_parse_from(&["", "TOTP"]).unwrap()
);
assert_eq!(
Opt {
arg: ArgChoice::Totp
},
Opt::try_parse_from(&["", "t"]).unwrap()
);
}
#[test]
fn skip_variant() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
#[allow(dead_code)] // silence warning about `Baz` being unused
enum ArgChoice {
Foo,
Bar,
#[clap(skip)]
Baz,
}
assert_eq!(
<ArgChoice as clap::ArgEnum>::value_variants()
.iter()
.map(clap::ArgEnum::to_possible_value)
.map(Option::unwrap)
.collect::<Vec<_>>(),
vec![
clap::PossibleValue::new("foo"),
clap::PossibleValue::new("bar")
]
);
{
use clap::ArgEnum;
assert!(ArgChoice::from_str("foo", true).is_ok());
assert!(ArgChoice::from_str("bar", true).is_ok());
assert!(ArgChoice::from_str("baz", true).is_err());
}
}
#[test]
fn skip_non_unit_variant() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
#[allow(dead_code)] // silence warning about `Baz` being unused
enum ArgChoice {
Foo,
Bar,
#[clap(skip)]
Baz(usize),
}
assert_eq!(
<ArgChoice as clap::ArgEnum>::value_variants()
.iter()
.map(clap::ArgEnum::to_possible_value)
.map(Option::unwrap)
.collect::<Vec<_>>(),
vec![
clap::PossibleValue::new("foo"),
clap::PossibleValue::new("bar")
]
);
{
use clap::ArgEnum;
assert!(ArgChoice::from_str("foo", true).is_ok());
assert!(ArgChoice::from_str("bar", true).is_ok());
assert!(ArgChoice::from_str("baz", true).is_err());
}
}
#[test]
fn from_str_invalid() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
}
{
use clap::ArgEnum;
assert!(ArgChoice::from_str("bar", true).is_err());
}
}
#[test]
fn option_type() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum)]
arg: Option<ArgChoice>,
}
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&[""]).unwrap());
assert_eq!(
Opt {
arg: Some(ArgChoice::Foo)
},
Opt::try_parse_from(&["", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(ArgChoice::Bar)
},
Opt::try_parse_from(&["", "bar"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "fOo"]).is_err());
}
#[test]
fn option_option_type() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, long)]
arg: Option<Option<ArgChoice>>,
}
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&[""]).unwrap());
assert_eq!(
Opt { arg: Some(None) },
Opt::try_parse_from(&["", "--arg"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(Some(ArgChoice::Foo))
},
Opt::try_parse_from(&["", "--arg", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(Some(ArgChoice::Bar))
},
Opt::try_parse_from(&["", "--arg", "bar"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "--arg", "fOo"]).is_err());
}
#[test]
fn vec_type() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, short, long)]
arg: Vec<ArgChoice>,
}
assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&[""]).unwrap());
assert_eq!(
Opt {
arg: vec![ArgChoice::Foo]
},
Opt::try_parse_from(&["", "-a", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&["", "-a", "foo", "-a", "bar"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "-a", "fOo"]).is_err());
}
#[test]
fn option_vec_type() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(arg_enum, short, long)]
arg: Option<Vec<ArgChoice>>,
}
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&[""]).unwrap());
assert_eq!(
Opt {
arg: Some(vec![ArgChoice::Foo])
},
Opt::try_parse_from(&["", "-a", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(vec![ArgChoice::Foo, ArgChoice::Bar])
},
Opt::try_parse_from(&["", "-a", "foo", "-a", "bar"]).unwrap()
);
assert!(Opt::try_parse_from(&["", "-a", "fOo"]).is_err());
}
#[test]
fn vec_type_default_value() {
#[derive(clap::ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
Baz,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(
arg_enum,
short,
long,
default_value = "foo,bar",
value_delimiter = ','
)]
arg: Vec<ArgChoice>,
}
assert_eq!(
Opt {
arg: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&[""]).unwrap()
);
assert_eq!(
Opt {
arg: vec![ArgChoice::Foo, ArgChoice::Baz]
},
Opt::try_parse_from(&["", "-a", "foo,baz"]).unwrap()
);
}

View file

@ -0,0 +1,120 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::CommandFactory;
use clap::Parser;
#[test]
fn required_argument() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
arg: i32,
}
assert_eq!(
Opt { arg: 42 },
Opt::try_parse_from(&["test", "42"]).unwrap()
);
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn argument_with_default() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(default_value = "42")]
arg: i32,
}
assert_eq!(
Opt { arg: 24 },
Opt::try_parse_from(&["test", "24"]).unwrap()
);
assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn auto_value_name() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
my_special_arg: i32,
}
let mut help = Vec::new();
Opt::command().write_help(&mut help).unwrap();
let help = String::from_utf8(help).unwrap();
assert!(help.contains("MY_SPECIAL_ARG"));
// Ensure the implicit `num_vals` is just 1
assert_eq!(
Opt { my_special_arg: 10 },
Opt::try_parse_from(&["test", "10"]).unwrap()
);
}
#[test]
fn explicit_value_name() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(value_name = "BROWNIE_POINTS")]
my_special_arg: i32,
}
let mut help = Vec::new();
Opt::command().write_help(&mut help).unwrap();
let help = String::from_utf8(help).unwrap();
assert!(help.contains("BROWNIE_POINTS"));
assert!(!help.contains("MY_SPECIAL_ARG"));
// Ensure the implicit `num_vals` is just 1
assert_eq!(
Opt { my_special_arg: 10 },
Opt::try_parse_from(&["test", "10"]).unwrap()
);
}
#[test]
fn option_type_is_optional() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
arg: Option<i32>,
}
assert_eq!(
Opt { arg: Some(42) },
Opt::try_parse_from(&["test", "42"]).unwrap()
);
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn vec_type_is_multiple_values() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
arg: Vec<i32>,
}
assert_eq!(
Opt { arg: vec![24] },
Opt::try_parse_from(&["test", "24"]).unwrap()
);
assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::try_parse_from(&["test", "24", "42"]).unwrap()
);
assert_eq!(
clap::ErrorKind::ValueValidation,
Opt::try_parse_from(&["test", "NOPE"]).err().unwrap().kind()
);
}

View file

@ -0,0 +1,51 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use crate::utils;
use clap::Parser;
#[test]
fn no_author_version_about() {
#[derive(Parser, PartialEq, Debug)]
#[clap(name = "foo")]
struct Opt {}
let output = utils::get_long_help::<Opt>();
assert!(output.starts_with("foo \n\nUSAGE:"));
}
#[test]
fn use_env() {
#[derive(Parser, PartialEq, Debug)]
#[clap(author, about, version)]
struct Opt {}
let output = utils::get_long_help::<Opt>();
assert!(output.starts_with("clap"));
assert!(output
.contains("A simple to use, efficient, and full-featured Command Line Argument Parser"));
}
#[test]
fn explicit_version_not_str_lit() {
const VERSION: &str = "custom version";
#[derive(Parser)]
#[clap(version = VERSION)]
pub struct Opt {}
let output = utils::get_long_help::<Opt>();
assert!(output.contains("custom version"));
}

View file

@ -0,0 +1,51 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Parser;
#[test]
fn basic() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short = 'a', long = "arg")]
arg: i32,
}
assert_eq!(
Opt { arg: 24 },
Opt::try_parse_from(&["test", "-a24"]).unwrap()
);
}
#[test]
fn update_basic() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short = 'a', long = "arg")]
single_value: i32,
}
let mut opt = Opt::try_parse_from(&["test", "-a0"]).unwrap();
opt.update_from(&["test", "-a42"]);
assert_eq!(Opt { single_value: 42 }, opt);
}
#[test]
fn unit_struct() {
#[derive(Parser, PartialEq, Debug)]
struct Opt;
assert_eq!(Opt {}, Opt::try_parse_from(&["test"]).unwrap());
}

View file

@ -0,0 +1,48 @@
use clap::{Args, Parser, Subcommand};
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(subcommand)]
sub: Box<Sub>,
}
#[derive(Subcommand, PartialEq, Debug)]
enum Sub {
Flame {
#[clap(flatten)]
arg: Box<Ext>,
},
}
#[derive(Args, PartialEq, Debug)]
struct Ext {
arg: u32,
}
#[test]
fn boxed_flatten_subcommand() {
assert_eq!(
Opt {
sub: Box::new(Sub::Flame {
arg: Box::new(Ext { arg: 1 })
})
},
Opt::try_parse_from(&["test", "flame", "1"]).unwrap()
);
}
#[test]
fn update_boxed_flatten_subcommand() {
let mut opt = Opt::try_parse_from(&["test", "flame", "1"]).unwrap();
opt.update_from(&["test", "flame", "42"]);
assert_eq!(
Opt {
sub: Box::new(Sub::Flame {
arg: Box::new(Ext { arg: 42 })
})
},
opt
);
}

View file

@ -0,0 +1,351 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Parser;
use std::ffi::{CString, OsStr};
use std::num::ParseIntError;
use std::path::PathBuf;
#[derive(Parser, PartialEq, Debug)]
struct PathOpt {
#[clap(short, long, parse(from_os_str))]
path: PathBuf,
#[clap(short, default_value = "../", parse(from_os_str))]
default_path: PathBuf,
#[clap(short, parse(from_os_str), multiple_occurrences(true))]
vector_path: Vec<PathBuf>,
#[clap(short, parse(from_os_str))]
option_path_1: Option<PathBuf>,
#[clap(short = 'q', parse(from_os_str))]
option_path_2: Option<PathBuf>,
}
#[test]
fn test_path_opt_simple() {
assert_eq!(
PathOpt {
path: PathBuf::from("/usr/bin"),
default_path: PathBuf::from("../"),
vector_path: vec![
PathBuf::from("/a/b/c"),
PathBuf::from("/d/e/f"),
PathBuf::from("/g/h/i"),
],
option_path_1: None,
option_path_2: Some(PathBuf::from("j.zip")),
},
PathOpt::try_parse_from(&[
"test", "-p", "/usr/bin", "-v", "/a/b/c", "-v", "/d/e/f", "-v", "/g/h/i", "-q",
"j.zip",
])
.unwrap()
);
}
fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(input, 16)
}
#[derive(Parser, PartialEq, Debug)]
struct HexOpt {
#[clap(short, parse(try_from_str = parse_hex))]
number: u64,
}
#[test]
fn test_parse_hex() {
assert_eq!(
HexOpt { number: 5 },
HexOpt::try_parse_from(&["test", "-n", "5"]).unwrap()
);
assert_eq!(
HexOpt {
number: 0x00ab_cdef
},
HexOpt::try_parse_from(&["test", "-n", "abcdef"]).unwrap()
);
let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err();
assert!(
err.to_string().contains("invalid digit found in string"),
"{}",
err
);
}
fn custom_parser_1(_: &str) -> &'static str {
"A"
}
#[derive(Debug)]
struct ErrCode(u32);
impl std::error::Error for ErrCode {}
impl std::fmt::Display for ErrCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
fn custom_parser_2(_: &str) -> Result<&'static str, ErrCode> {
Ok("B")
}
fn custom_parser_3(_: &OsStr) -> &'static str {
"C"
}
fn custom_parser_4(_: &OsStr) -> Result<&'static str, String> {
Ok("D")
}
#[derive(Parser, PartialEq, Debug)]
struct NoOpOpt {
#[clap(short, parse(from_str = custom_parser_1))]
a: &'static str,
#[clap(short, parse(try_from_str = custom_parser_2))]
b: &'static str,
#[clap(short, parse(from_os_str = custom_parser_3))]
c: &'static str,
#[clap(short, parse(try_from_os_str = custom_parser_4))]
d: &'static str,
}
#[test]
fn test_every_custom_parser() {
assert_eq!(
NoOpOpt {
a: "A",
b: "B",
c: "C",
d: "D"
},
NoOpOpt::try_parse_from(&["test", "-a=?", "-b=?", "-c=?", "-d=?"]).unwrap()
);
}
#[test]
fn update_every_custom_parser() {
let mut opt = NoOpOpt {
a: "0",
b: "0",
c: "0",
d: "D",
};
opt.try_update_from(&["test", "-a=?", "-b=?", "-d=?"])
.unwrap();
assert_eq!(
NoOpOpt {
a: "A",
b: "B",
c: "0",
d: "D"
},
opt
);
}
// Note: can't use `Vec<u8>` directly, as clap would instead look for
// conversion function from `&str` to `u8`.
type Bytes = Vec<u8>;
#[derive(Parser, PartialEq, Debug)]
struct DefaultedOpt {
#[clap(short, parse(from_str))]
bytes: Bytes,
#[clap(short, parse(try_from_str))]
integer: u64,
#[clap(short, parse(from_os_str))]
path: PathBuf,
}
#[test]
fn test_parser_with_default_value() {
assert_eq!(
DefaultedOpt {
bytes: b"E\xc2\xb2=p\xc2\xb2c\xc2\xb2+m\xc2\xb2c\xe2\x81\xb4".to_vec(),
integer: 9000,
path: PathBuf::from("src/lib.rs"),
},
DefaultedOpt::try_parse_from(&[
"test",
"-b",
"E²=p²c²+m²c⁴",
"-i",
"9000",
"-p",
"src/lib.rs",
])
.unwrap()
);
}
#[derive(PartialEq, Debug)]
struct Foo(u8);
fn foo(value: u64) -> Foo {
Foo(value as u8)
}
#[derive(Parser, PartialEq, Debug)]
struct Occurrences {
#[clap(short, long, parse(from_occurrences))]
signed: i32,
#[clap(short, parse(from_occurrences))]
little_signed: i8,
#[clap(short, parse(from_occurrences))]
unsigned: usize,
#[clap(short = 'r', parse(from_occurrences))]
little_unsigned: u8,
#[clap(short, long, parse(from_occurrences = foo))]
custom: Foo,
}
#[test]
fn test_parser_occurrences() {
assert_eq!(
Occurrences {
signed: 3,
little_signed: 1,
unsigned: 0,
little_unsigned: 4,
custom: Foo(5),
},
Occurrences::try_parse_from(&[
"test", "-s", "--signed", "--signed", "-l", "-rrrr", "-cccc", "--custom",
])
.unwrap()
);
}
#[test]
fn test_custom_bool() {
fn parse_bool(s: &str) -> Result<bool, String> {
match s {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(format!("invalid bool {}", s)),
}
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, parse(try_from_str = parse_bool))]
debug: bool,
#[clap(
short,
default_value = "false",
parse(try_from_str = parse_bool)
)]
verbose: bool,
#[clap(short, parse(try_from_str = parse_bool))]
tribool: Option<bool>,
#[clap(short, parse(try_from_str = parse_bool), multiple_occurrences(true))]
bitset: Vec<bool>,
}
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "-d"]).is_err());
assert!(Opt::try_parse_from(&["test", "-dfoo"]).is_err());
assert_eq!(
Opt {
debug: false,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::try_parse_from(&["test", "-dfalse"]).unwrap()
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::try_parse_from(&["test", "-dtrue"]).unwrap()
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::try_parse_from(&["test", "-dtrue", "-vfalse"]).unwrap()
);
assert_eq!(
Opt {
debug: true,
verbose: true,
tribool: None,
bitset: vec![],
},
Opt::try_parse_from(&["test", "-dtrue", "-vtrue"]).unwrap()
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: Some(false),
bitset: vec![],
},
Opt::try_parse_from(&["test", "-dtrue", "-tfalse"]).unwrap()
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: Some(true),
bitset: vec![],
},
Opt::try_parse_from(&["test", "-dtrue", "-ttrue"]).unwrap()
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![false, true, false, false],
},
Opt::try_parse_from(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"])
.unwrap()
);
}
#[test]
fn test_cstring() {
#[derive(Parser)]
struct Opt {
#[clap(parse(try_from_str = CString::new))]
c_string: CString,
}
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt::try_parse_from(&["test", "bla"])
.unwrap()
.c_string
.to_bytes(),
b"bla"
);
assert!(Opt::try_parse_from(&["test", "bla\0bla"]).is_err());
}

View file

@ -0,0 +1,57 @@
use clap::{CommandFactory, Parser};
use crate::utils;
#[test]
fn default_value() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(default_value = "3")]
arg: i32,
}
assert_eq!(Opt { arg: 3 }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(Opt { arg: 1 }, Opt::try_parse_from(&["test", "1"]).unwrap());
let help = utils::get_long_help::<Opt>();
assert!(help.contains("[default: 3]"));
}
#[test]
fn default_value_t() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(default_value_t = 3)]
arg: i32,
}
assert_eq!(Opt { arg: 3 }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(Opt { arg: 1 }, Opt::try_parse_from(&["test", "1"]).unwrap());
let help = utils::get_long_help::<Opt>();
assert!(help.contains("[default: 3]"));
}
#[test]
fn auto_default_value_t() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(default_value_t)]
arg: i32,
}
assert_eq!(Opt { arg: 0 }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(Opt { arg: 1 }, Opt::try_parse_from(&["test", "1"]).unwrap());
let help = utils::get_long_help::<Opt>();
assert!(help.contains("[default: 0]"));
}
#[test]
fn detect_os_variant() {
#![allow(unused_parens)] // needed for `as_ref` call
#[derive(clap::Parser)]
pub struct Options {
#[clap(default_value_os = ("123".as_ref()))]
x: String,
}
Options::command().debug_assert();
}

View file

@ -0,0 +1,53 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#![deny(warnings)]
use clap::Parser;
fn try_str(s: &str) -> Result<String, std::convert::Infallible> {
Ok(s.into())
}
#[test]
fn warning_never_struct() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(parse(try_from_str = try_str), default_value_t)]
s: String,
}
assert_eq!(
Opt {
s: "foo".to_string()
},
Opt::try_parse_from(&["test", "foo"]).unwrap()
);
}
#[test]
fn warning_never_enum() {
#[derive(Parser, Debug, PartialEq)]
enum Opt {
Foo {
#[clap(parse(try_from_str = try_str), default_value_t)]
s: String,
},
}
assert_eq!(
Opt::Foo {
s: "foo".to_string()
},
Opt::try_parse_from(&["test", "foo", "foo"]).unwrap()
);
}

View file

@ -0,0 +1,240 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use crate::utils;
use clap::{ArgEnum, CommandFactory, Parser};
#[test]
fn doc_comments() {
/// Lorem ipsum
#[derive(Parser, PartialEq, Debug)]
struct LoremIpsum {
/// Fooify a bar
/// and a baz
#[clap(short, long)]
foo: bool,
}
let help = utils::get_long_help::<LoremIpsum>();
assert!(help.contains("Lorem ipsum"));
assert!(help.contains("Fooify a bar and a baz"));
}
#[test]
fn help_is_better_than_comments() {
/// Lorem ipsum
#[derive(Parser, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
/// Fooify a bar
#[clap(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")]
foo: bool,
}
let help = utils::get_long_help::<LoremIpsum>();
assert!(help.contains("Dolor sit amet"));
assert!(!help.contains("Lorem ipsum"));
assert!(help.contains("DO NOT PASS A BAR"));
}
#[test]
fn empty_line_in_doc_comment_is_double_linefeed() {
/// Foo.
///
/// Bar
#[derive(Parser, PartialEq, Debug)]
#[clap(name = "lorem-ipsum")]
struct LoremIpsum {}
let help = utils::get_long_help::<LoremIpsum>();
assert!(help.starts_with("lorem-ipsum \nFoo.\n\nBar\n\nUSAGE:"));
}
#[test]
fn field_long_doc_comment_both_help_long_help() {
/// Lorem ipsumclap
#[derive(Parser, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
/// Dot is removed from multiline comments.
///
/// Long help
#[clap(long)]
foo: bool,
/// Dot is removed from one short comment.
#[clap(long)]
bar: bool,
}
let short_help = utils::get_help::<LoremIpsum>();
let long_help = utils::get_long_help::<LoremIpsum>();
assert!(short_help.contains("Dot is removed from one short comment"));
assert!(!short_help.contains("Dot is removed from one short comment."));
assert!(short_help.contains("Dot is removed from multiline comments"));
assert!(!short_help.contains("Dot is removed from multiline comments."));
assert!(long_help.contains("Long help"));
assert!(!short_help.contains("Long help"));
}
#[test]
fn top_long_doc_comment_both_help_long_help() {
/// Lorem ipsumclap
#[derive(Parser, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
#[clap(subcommand)]
foo: SubCommand,
}
#[derive(Parser, Debug)]
pub enum SubCommand {
/// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES
///
/// Or something else
Foo {
#[clap(help = "foo")]
bars: String,
},
}
let short_help = utils::get_help::<LoremIpsum>();
let long_help = utils::get_subcommand_long_help::<LoremIpsum>("foo");
assert!(!short_help.contains("Or something else"));
assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES"));
assert!(long_help.contains("Or something else"));
}
#[test]
fn verbatim_doc_comment() {
/// DANCE!
///
/// ()
/// |
/// ( () )
/// ) ________ // )
/// () |\ \ //
/// ( \\__ \ ______\//
/// \__) | |
/// | | |
/// \ | |
/// \|_______|
/// // \\
/// (( ||
/// \\ ||
/// ( () ||
/// ( () ) )
#[derive(Parser, Debug)]
#[clap(verbatim_doc_comment)]
struct SeeFigure1 {
#[clap(long)]
foo: bool,
}
let help = utils::get_long_help::<SeeFigure1>();
let sample = r#"
()
|
( () )
) ________ // )
() |\ \ //
( \\__ \ ______\//
\__) | |
| | |
\ | |
\|_______|
// \\
(( ||
\\ ||
( () ||
( () ) )"#;
assert!(help.contains(sample))
}
#[test]
fn verbatim_doc_comment_field() {
#[derive(Parser, Debug)]
struct Command {
/// This help ends in a period.
#[clap(long, verbatim_doc_comment)]
foo: bool,
/// This help does not end in a period.
#[clap(long)]
bar: bool,
}
let help = utils::get_long_help::<Command>();
assert!(help.contains("This help ends in a period."));
assert!(help.contains("This help does not end in a period"));
}
#[test]
fn multiline_separates_default() {
#[derive(Parser, Debug)]
struct Command {
/// Multiline
///
/// Doc comment
#[clap(long, default_value = "x")]
x: String,
}
let help = utils::get_long_help::<Command>();
assert!(!help.contains("Doc comment [default"));
assert!(help.lines().any(|s| s.trim().starts_with("[default")));
// The short help should still have the default on the same line
let help = utils::get_help::<Command>();
assert!(help.contains("Multiline [default"));
}
#[test]
fn argenum_multiline_doc_comment() {
#[derive(ArgEnum, Clone)]
enum LoremIpsum {
/// Multiline
///
/// Doc comment
Bar,
}
}
#[test]
fn doc_comment_about_handles_both_abouts() {
/// Opts doc comment summary
#[derive(Parser, Debug)]
pub struct Opts {
#[clap(subcommand)]
pub cmd: Sub,
}
/// Sub doc comment summary
///
/// Sub doc comment body
#[derive(Parser, PartialEq, Eq, Debug)]
pub enum Sub {
Compress { output: String },
}
let cmd = Opts::command();
assert_eq!(cmd.get_about(), Some("Opts doc comment summary"));
// clap will fallback to `about` on `None`. The main care about is not providing a `Sub` doc
// comment.
assert_eq!(cmd.get_long_about(), None);
}

View file

@ -0,0 +1,36 @@
use crate::utils;
use clap::Parser;
#[test]
fn explicit_short_long_no_rename() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short = '.', long = ".foo")]
foo: String,
}
assert_eq!(
Opt { foo: "long".into() },
Opt::try_parse_from(&["test", "--.foo", "long"]).unwrap()
);
assert_eq!(
Opt {
foo: "short".into(),
},
Opt::try_parse_from(&["test", "-.", "short"]).unwrap()
);
}
#[test]
fn explicit_name_no_rename() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(name = ".options")]
foo: String,
}
let help = utils::get_long_help::<Opt>();
assert!(help.contains("<.options>"))
}

View file

@ -0,0 +1,189 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Parser;
#[test]
fn bool_type_is_flag() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
alice: bool,
}
assert_eq!(
Opt { alice: false },
Opt::try_parse_from(&["test"]).unwrap()
);
assert_eq!(
Opt { alice: true },
Opt::try_parse_from(&["test", "-a"]).unwrap()
);
assert_eq!(
Opt { alice: true },
Opt::try_parse_from(&["test", "--alice"]).unwrap()
);
assert!(Opt::try_parse_from(&["test", "-i"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "-a"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "--alice"]).is_err());
}
#[test]
fn from_occurrences() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long, parse(from_occurrences))]
alice: u64,
#[clap(short, long, parse(from_occurrences))]
bob: u8,
}
assert_eq!(
Opt { alice: 0, bob: 0 },
Opt::try_parse_from(&["test"]).unwrap()
);
assert_eq!(
Opt { alice: 1, bob: 0 },
Opt::try_parse_from(&["test", "-a"]).unwrap()
);
assert_eq!(
Opt { alice: 2, bob: 0 },
Opt::try_parse_from(&["test", "-a", "-a"]).unwrap()
);
assert_eq!(
Opt { alice: 2, bob: 2 },
Opt::try_parse_from(&["test", "-a", "--alice", "-bb"]).unwrap()
);
assert_eq!(
Opt { alice: 3, bob: 1 },
Opt::try_parse_from(&["test", "-aaa", "--bob"]).unwrap()
);
assert!(Opt::try_parse_from(&["test", "-i"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err());
}
#[test]
fn non_bool_type_flag() {
fn parse_from_flag(b: bool) -> std::sync::atomic::AtomicBool {
std::sync::atomic::AtomicBool::new(b)
}
#[derive(Parser, Debug)]
struct Opt {
#[clap(short, long, parse(from_flag = parse_from_flag))]
alice: std::sync::atomic::AtomicBool,
#[clap(short, long, parse(from_flag))]
bob: std::sync::atomic::AtomicBool,
}
let falsey = Opt::try_parse_from(&["test"]).unwrap();
assert!(!falsey.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(!falsey.bob.load(std::sync::atomic::Ordering::Relaxed));
let alice = Opt::try_parse_from(&["test", "-a"]).unwrap();
assert!(alice.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(!alice.bob.load(std::sync::atomic::Ordering::Relaxed));
let bob = Opt::try_parse_from(&["test", "-b"]).unwrap();
assert!(!bob.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(bob.bob.load(std::sync::atomic::Ordering::Relaxed));
let both = Opt::try_parse_from(&["test", "-b", "-a"]).unwrap();
assert!(both.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(both.bob.load(std::sync::atomic::Ordering::Relaxed));
}
#[test]
fn mixed_type_flags() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
alice: bool,
#[clap(short, long, parse(from_occurrences))]
bob: u64,
}
assert_eq!(
Opt {
alice: false,
bob: 0
},
Opt::try_parse_from(&["test"]).unwrap()
);
assert_eq!(
Opt {
alice: true,
bob: 0
},
Opt::try_parse_from(&["test", "-a"]).unwrap()
);
assert_eq!(
Opt {
alice: true,
bob: 0
},
Opt::try_parse_from(&["test", "-a"]).unwrap()
);
assert_eq!(
Opt {
alice: false,
bob: 1
},
Opt::try_parse_from(&["test", "-b"]).unwrap()
);
assert_eq!(
Opt {
alice: true,
bob: 1
},
Opt::try_parse_from(&["test", "--alice", "--bob"]).unwrap()
);
assert_eq!(
Opt {
alice: true,
bob: 4
},
Opt::try_parse_from(&["test", "-bb", "-a", "-bb"]).unwrap()
);
}
#[test]
fn ignore_qualified_bool_type() {
mod inner {
#[allow(non_camel_case_types)]
#[derive(PartialEq, Debug)]
pub struct bool(pub String);
impl std::str::FromStr for self::bool {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(self::bool(s.into()))
}
}
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
arg: inner::bool,
}
assert_eq!(
Opt {
arg: inner::bool("success".into())
},
Opt::try_parse_from(&["test", "success"]).unwrap()
);
}

View file

@ -0,0 +1,256 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use crate::utils;
use clap::{Args, Parser, Subcommand};
#[test]
fn flatten() {
#[derive(Args, PartialEq, Debug)]
struct Common {
arg: i32,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(flatten)]
common: Common,
}
assert_eq!(
Opt {
common: Common { arg: 42 }
},
Opt::try_parse_from(&["test", "42"]).unwrap()
);
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn flatten_twice() {
#[derive(Args, PartialEq, Debug)]
struct Common {
arg: i32,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(flatten)]
c1: Common,
// Defines "arg" twice, so this should not work.
#[clap(flatten)]
c2: Common,
}
Opt::try_parse_from(&["test", "42", "43"]).unwrap();
}
#[test]
fn flatten_in_subcommand() {
#[derive(Args, PartialEq, Debug)]
struct Common {
arg: i32,
}
#[derive(Args, PartialEq, Debug)]
struct Add {
#[clap(short)]
interactive: bool,
#[clap(flatten)]
common: Common,
}
#[derive(Parser, PartialEq, Debug)]
enum Opt {
Fetch {
#[clap(short)]
all: bool,
#[clap(flatten)]
common: Common,
},
Add(Add),
}
assert_eq!(
Opt::Fetch {
all: false,
common: Common { arg: 42 }
},
Opt::try_parse_from(&["test", "fetch", "42"]).unwrap()
);
assert_eq!(
Opt::Add(Add {
interactive: true,
common: Common { arg: 43 }
}),
Opt::try_parse_from(&["test", "add", "-i", "43"]).unwrap()
);
}
#[test]
fn update_args_with_flatten() {
#[derive(Args, PartialEq, Debug)]
struct Common {
arg: i32,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(flatten)]
common: Common,
}
let mut opt = Opt {
common: Common { arg: 42 },
};
opt.try_update_from(&["test"]).unwrap();
assert_eq!(Opt::try_parse_from(&["test", "42"]).unwrap(), opt);
let mut opt = Opt {
common: Common { arg: 42 },
};
opt.try_update_from(&["test", "52"]).unwrap();
assert_eq!(Opt::try_parse_from(&["test", "52"]).unwrap(), opt);
}
#[derive(Subcommand, PartialEq, Debug)]
enum BaseCli {
Command1(Command1),
}
#[derive(Args, PartialEq, Debug)]
struct Command1 {
arg1: i32,
arg2: i32,
}
#[derive(Args, PartialEq, Debug)]
struct Command2 {
arg2: i32,
}
#[derive(Parser, PartialEq, Debug)]
enum Opt {
#[clap(flatten)]
BaseCli(BaseCli),
Command2(Command2),
}
#[test]
fn merge_subcommands_with_flatten() {
assert_eq!(
Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 42, arg2: 44 })),
Opt::try_parse_from(&["test", "command1", "42", "44"]).unwrap()
);
assert_eq!(
Opt::Command2(Command2 { arg2: 43 }),
Opt::try_parse_from(&["test", "command2", "43"]).unwrap()
);
}
#[test]
fn update_subcommands_with_flatten() {
let mut opt = Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 12, arg2: 14 }));
opt.try_update_from(&["test", "command1", "42", "44"])
.unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "command1", "42", "44"]).unwrap(),
opt
);
let mut opt = Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 12, arg2: 14 }));
opt.try_update_from(&["test", "command1", "42"]).unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "command1", "42", "14"]).unwrap(),
opt
);
let mut opt = Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 12, arg2: 14 }));
opt.try_update_from(&["test", "command2", "43"]).unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "command2", "43"]).unwrap(),
opt
);
}
#[test]
fn flatten_with_doc_comment() {
#[derive(Args, PartialEq, Debug)]
struct Common {
/// This is an arg. Arg means "argument". Command line argument.
arg: i32,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
/// The very important comment that clippy had me put here.
/// It knows better.
#[clap(flatten)]
common: Common,
}
assert_eq!(
Opt {
common: Common { arg: 42 }
},
Opt::try_parse_from(&["test", "42"]).unwrap()
);
let help = utils::get_help::<Opt>();
assert!(help.contains("This is an arg."));
assert!(!help.contains("The very important"));
}
#[test]
fn docstrings_ordering_with_multiple_clap() {
/// This is the docstring for Flattened
#[derive(Args)]
struct Flattened {
#[clap(long)]
foo: bool,
}
/// This is the docstring for Command
#[derive(Parser)]
struct Command {
#[clap(flatten)]
flattened: Flattened,
}
let short_help = utils::get_help::<Command>();
assert!(short_help.contains("This is the docstring for Command"));
}
#[test]
fn docstrings_ordering_with_multiple_clap_partial() {
/// This is the docstring for Flattened
#[derive(Args)]
struct Flattened {
#[clap(long)]
foo: bool,
}
#[derive(Parser)]
struct Command {
#[clap(flatten)]
flattened: Flattened,
}
let short_help = utils::get_help::<Command>();
assert!(short_help.contains("This is the docstring for Flattened"));
}

View file

@ -0,0 +1,145 @@
use clap::{Args, Parser};
#[test]
fn generic_struct_flatten() {
#[derive(Args, PartialEq, Debug)]
struct Inner {
pub answer: isize,
}
#[derive(Parser, PartialEq, Debug)]
struct Outer<T: Args> {
#[clap(flatten)]
pub inner: T,
}
assert_eq!(
Outer {
inner: Inner { answer: 42 }
},
Outer::parse_from(&["--answer", "42"])
)
}
#[test]
fn generic_struct_flatten_w_where_clause() {
#[derive(Args, PartialEq, Debug)]
struct Inner {
pub answer: isize,
}
#[derive(Parser, PartialEq, Debug)]
struct Outer<T>
where
T: Args,
{
#[clap(flatten)]
pub inner: T,
}
assert_eq!(
Outer {
inner: Inner { answer: 42 }
},
Outer::parse_from(&["--answer", "42"])
)
}
#[test]
fn generic_enum() {
#[derive(Args, PartialEq, Debug)]
struct Inner {
pub answer: isize,
}
#[derive(Parser, PartialEq, Debug)]
enum GenericEnum<T: Args> {
Start(T),
Stop,
}
assert_eq!(
GenericEnum::Start(Inner { answer: 42 }),
GenericEnum::parse_from(&["test", "start", "42"])
)
}
#[test]
fn generic_enum_w_where_clause() {
#[derive(Args, PartialEq, Debug)]
struct Inner {
pub answer: isize,
}
#[derive(Parser, PartialEq, Debug)]
enum GenericEnum<T>
where
T: Args,
{
Start(T),
Stop,
}
assert_eq!(
GenericEnum::Start(Inner { answer: 42 }),
GenericEnum::parse_from(&["test", "start", "42"])
)
}
#[test]
fn generic_w_fromstr_trait_bound() {
use std::str::FromStr;
#[derive(Parser, PartialEq, Debug)]
struct Opt<T>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error + Sync + Send + 'static,
{
answer: T,
}
assert_eq!(
Opt::<isize> { answer: 42 },
Opt::<isize>::parse_from(&["--answer", "42"])
)
}
#[test]
fn generic_wo_trait_bound() {
use std::time::Duration;
#[derive(Parser, PartialEq, Debug)]
struct Opt<T> {
answer: isize,
#[clap(skip)]
took: Option<T>,
}
assert_eq!(
Opt::<Duration> {
answer: 42,
took: None
},
Opt::<Duration>::parse_from(&["--answer", "42"])
)
}
#[test]
fn generic_where_clause_w_trailing_comma() {
use std::str::FromStr;
#[derive(Parser, PartialEq, Debug)]
struct Opt<T>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error + Sync + Send + 'static,
{
pub answer: T,
}
assert_eq!(
Opt::<isize> { answer: 42 },
Opt::<isize>::parse_from(&["--answer", "42"])
)
}

473
tests/derive/legacy/help.rs Normal file
View file

@ -0,0 +1,473 @@
use clap::{AppSettings, 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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "should_be_in_section_a")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "no_section")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "should_be_in_section_a")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "should_be_in_default_section")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "should_be_in_section_a")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "should_be_in_section_b")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "should_be_in_default_section")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
sub_a_two
.get_arguments()
.find(|a| a.get_id() == "should_be_in_sub_a")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
sub_b_one
.get_arguments()
.find(|a| a.get_id() == "should_be_in_sub_b")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
sub_c_one
.get_arguments()
.find(|a| a.get_id() == "should_be_in_sub_c")
.unwrap()
} else {
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 = if cfg!(feature = "unstable-v4") {
cmd.get_arguments()
.find(|a| a.get_id() == "should_be_in_section_a")
.unwrap()
} else {
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, parse(from_occurrences))]
verbose: u8,
},
}
let result = Opts::try_parse_from(&["test", "sub"]);
assert!(
result.is_err(),
"`SubcommandsNegateReqs` with non-optional `req_str` should fail: {:?}",
result.unwrap()
);
if cfg!(feature = "unstable-v4") {
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);
} else {
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")]
#[clap(setting = AppSettings::DeriveDisplayOrder)]
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();
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(setting = AppSettings::DeriveDisplayOrder)]
#[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();
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(setting = AppSettings::DeriveDisplayOrder)]
#[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();
assert_eq!(help, HELP);
}

View file

@ -0,0 +1,130 @@
// https://github.com/TeXitoi/structopt/issues/{NUMBER}
use crate::utils;
use clap::{ArgGroup, Args, Parser, Subcommand};
#[test]
fn issue_151() {
#[derive(Args, Debug)]
#[clap(group = ArgGroup::new("verb").required(true).multiple(true))]
struct Opt {
#[clap(long, group = "verb")]
foo: bool,
#[clap(long, group = "verb")]
bar: bool,
}
#[derive(Debug, Parser)]
struct Cli {
#[clap(flatten)]
a: Opt,
}
assert!(Cli::try_parse_from(&["test"]).is_err());
assert!(Cli::try_parse_from(&["test", "--foo"]).is_ok());
assert!(Cli::try_parse_from(&["test", "--bar"]).is_ok());
assert!(Cli::try_parse_from(&["test", "--zebra"]).is_err());
assert!(Cli::try_parse_from(&["test", "--foo", "--bar"]).is_ok());
}
#[test]
fn issue_289() {
#[derive(Parser)]
#[clap(infer_subcommands = true)]
enum Args {
SomeCommand {
#[clap(subcommand)]
sub: SubSubCommand,
},
AnotherCommand,
}
#[derive(Subcommand)]
#[clap(infer_subcommands = true)]
enum SubSubCommand {
TestCommand,
}
assert!(Args::try_parse_from(&["test", "some-command", "test-command"]).is_ok());
assert!(Args::try_parse_from(&["test", "some", "test-command"]).is_ok());
assert!(Args::try_parse_from(&["test", "some-command", "test"]).is_ok());
assert!(Args::try_parse_from(&["test", "some", "test"]).is_ok());
}
#[test]
fn issue_324() {
fn my_version() -> &'static str {
"MY_VERSION"
}
#[derive(Parser)]
#[clap(version = my_version())]
struct Opt {
#[clap(subcommand)]
_cmd: SubCommand,
}
#[derive(Subcommand)]
enum SubCommand {
Start,
}
let help = utils::get_long_help::<Opt>();
assert!(help.contains("MY_VERSION"));
}
#[test]
fn issue_418() {
#[derive(Debug, Parser)]
struct Opts {
#[clap(subcommand)]
/// The command to run
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Reticulate the splines
#[clap(visible_alias = "ret")]
Reticulate {
/// How many splines
num_splines: u8,
},
/// Frobnicate the rest
#[clap(visible_alias = "frob")]
Frobnicate,
}
let help = utils::get_long_help::<Opts>();
assert!(help.contains("Reticulate the splines [aliases: ret]"));
}
#[test]
fn issue_490() {
use clap::Parser;
use std::iter::FromIterator;
use std::str::FromStr;
struct U16ish;
impl FromStr for U16ish {
type Err = ();
fn from_str(_: &str) -> Result<Self, Self::Err> {
unimplemented!()
}
}
impl<'a> FromIterator<&'a U16ish> for Vec<u16> {
fn from_iter<T: IntoIterator<Item = &'a U16ish>>(_: T) -> Self {
unimplemented!()
}
}
#[derive(Parser, Debug)]
struct Opt {
opt_vec: Vec<u16>,
#[clap(long)]
opt_opt_vec: Option<Vec<u16>>,
}
// Assert that it compiles
}

View file

@ -0,0 +1,53 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Parser;
// Tests that clap_derive properly detects an `Option` field
// that results from a macro expansion
#[test]
fn use_option() {
macro_rules! expand_ty {
($name:ident: $ty:ty) => {
#[derive(Parser)]
struct Outer {
#[clap(short, long)]
#[allow(dead_code)]
$name: $ty,
}
};
}
expand_ty!(my_field: Option<String>);
}
#[test]
fn issue_447() {
macro_rules! Command {
( $name:ident, [
#[$meta:meta] $var:ident($inner:ty)
] ) => {
#[derive(Debug, PartialEq, clap::Parser)]
enum $name {
#[$meta]
$var($inner),
}
};
}
Command! {GitCmd, [
#[clap(external_subcommand)]
Ext(Vec<String>)
]}
}

View file

@ -0,0 +1,33 @@
#![allow(deprecated)]
mod app_name;
mod arg_enum;
mod arguments;
mod author_version_about;
mod basic;
mod boxed;
mod custom_string_parsers;
mod default_value;
mod deny_warnings;
mod doc_comments_help;
mod explicit_name_no_renaming;
mod flags;
mod flatten;
mod generic;
mod help;
mod issues;
mod macros;
mod naming;
mod nested_subcommands;
mod non_literal_attributes;
mod options;
mod privacy;
mod raw_bool_literal;
mod raw_idents;
mod rename_all_env;
mod skip;
mod structopt;
mod subcommands;
mod type_alias_regressions;
mod utf8;
mod utils;

View file

@ -0,0 +1,354 @@
use clap::Parser;
#[test]
fn test_standalone_long_generates_kebab_case() {
#[derive(Parser, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Opt {
#[clap(long)]
FOO_OPTION: bool,
}
assert_eq!(
Opt { FOO_OPTION: true },
Opt::try_parse_from(&["test", "--foo-option"]).unwrap()
);
}
#[test]
fn test_custom_long_overwrites_default_name() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(long = "foo")]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "--foo"]).unwrap()
);
}
#[test]
fn test_standalone_long_uses_previous_defined_custom_name() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(name = "foo", long)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "--foo"]).unwrap()
);
}
#[test]
fn test_standalone_long_ignores_afterwards_defined_custom_name() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(long, name = "foo")]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "--foo-option"]).unwrap()
);
}
#[test]
fn test_standalone_short_generates_kebab_case() {
#[derive(Parser, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Opt {
#[clap(short)]
FOO_OPTION: bool,
}
assert_eq!(
Opt { FOO_OPTION: true },
Opt::try_parse_from(&["test", "-f"]).unwrap()
);
}
#[test]
fn test_custom_short_overwrites_default_name() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(short = 'o')]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "-o"]).unwrap()
);
}
#[test]
fn test_standalone_short_uses_previous_defined_custom_name() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(name = "option", short)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "-o"]).unwrap()
);
}
#[test]
fn test_standalone_short_ignores_afterwards_defined_custom_name() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(short, name = "option")]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "-f"]).unwrap()
);
}
#[test]
fn test_standalone_long_uses_previous_defined_casing() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "screaming_snake", long)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "--FOO_OPTION"]).unwrap()
);
}
#[test]
fn test_standalone_short_uses_previous_defined_casing() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "screaming_snake", short)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "-F"]).unwrap()
);
}
#[test]
fn test_standalone_long_works_with_verbatim_casing() {
#[derive(Parser, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Opt {
#[clap(rename_all = "verbatim", long)]
_fOO_oPtiON: bool,
}
assert_eq!(
Opt { _fOO_oPtiON: true },
Opt::try_parse_from(&["test", "--_fOO_oPtiON"]).unwrap()
);
}
#[test]
fn test_standalone_short_works_with_verbatim_casing() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "verbatim", short)]
_foo: bool,
}
assert_eq!(
Opt { _foo: true },
Opt::try_parse_from(&["test", "-_"]).unwrap()
);
}
#[test]
fn test_rename_all_is_propagated_from_struct_to_fields() {
#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
struct Opt {
#[clap(long)]
foo: bool,
}
assert_eq!(
Opt { foo: true },
Opt::try_parse_from(&["test", "--FOO"]).unwrap()
);
}
#[test]
fn test_rename_all_is_not_propagated_from_struct_into_flattened() {
#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
struct Opt {
#[clap(flatten)]
foo: Foo,
}
#[derive(Parser, Debug, PartialEq)]
struct Foo {
#[clap(long)]
foo: bool,
}
assert_eq!(
Opt {
foo: Foo { foo: true }
},
Opt::try_parse_from(&["test", "--foo"]).unwrap()
);
}
#[test]
fn test_lower_is_renamed() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "lower", long)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "--foooption"]).unwrap()
);
}
#[test]
fn test_upper_is_renamed() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "upper", long)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::try_parse_from(&["test", "--FOOOPTION"]).unwrap()
);
}
#[test]
fn test_single_word_enum_variant_is_default_renamed_into_kebab_case() {
#[derive(Parser, Debug, PartialEq)]
enum Opt {
Command { foo: u32 },
}
assert_eq!(
Opt::Command { foo: 0 },
Opt::try_parse_from(&["test", "command", "0"]).unwrap()
);
}
#[test]
fn test_multi_word_enum_variant_is_renamed() {
#[derive(Parser, Debug, PartialEq)]
enum Opt {
FirstCommand { foo: u32 },
}
assert_eq!(
Opt::FirstCommand { foo: 0 },
Opt::try_parse_from(&["test", "first-command", "0"]).unwrap()
);
}
#[test]
fn test_rename_all_is_not_propagated_from_struct_into_subcommand() {
#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
struct Opt {
#[clap(subcommand)]
foo: Foo,
}
#[derive(Parser, Debug, PartialEq)]
enum Foo {
Command {
#[clap(long)]
foo: bool,
},
}
assert_eq!(
Opt {
foo: Foo::Command { foo: true }
},
Opt::try_parse_from(&["test", "command", "--foo"]).unwrap()
);
}
#[test]
fn test_rename_all_is_propagated_from_enum_to_variants() {
#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
enum Opt {
FirstVariant,
SecondVariant {
#[clap(long)]
foo: String,
},
}
assert_eq!(
Opt::FirstVariant,
Opt::try_parse_from(&["test", "FIRST_VARIANT"]).unwrap()
);
}
#[test]
fn test_rename_all_is_propagated_from_enum_to_variant_fields() {
#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
enum Opt {
FirstVariant,
SecondVariant {
#[clap(long)]
foo: String,
},
}
assert_eq!(
Opt::SecondVariant {
foo: "value".into()
},
Opt::try_parse_from(&["test", "SECOND_VARIANT", "--FOO", "value"]).unwrap()
);
}
#[test]
fn test_rename_all_is_propagation_can_be_overridden() {
#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
enum Opt {
#[clap(rename_all = "kebab_case")]
FirstVariant {
#[clap(long)]
foo_option: bool,
},
SecondVariant {
#[clap(rename_all = "kebab_case", long)]
foo_option: bool,
},
}
assert_eq!(
Opt::FirstVariant { foo_option: true },
Opt::try_parse_from(&["test", "first-variant", "--foo-option"]).unwrap()
);
assert_eq!(
Opt::SecondVariant { foo_option: true },
Opt::try_parse_from(&["test", "SECOND_VARIANT", "--foo-option"]).unwrap()
);
}

View file

@ -0,0 +1,194 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::{Parser, Subcommand};
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
force: bool,
#[clap(short, long, parse(from_occurrences))]
verbose: u64,
#[clap(subcommand)]
cmd: Sub,
}
#[derive(Subcommand, PartialEq, Debug)]
enum Sub {
Fetch {},
Add {},
}
#[derive(Parser, PartialEq, Debug)]
struct Opt2 {
#[clap(short, long)]
force: bool,
#[clap(short, long, parse(from_occurrences))]
verbose: u64,
#[clap(subcommand)]
cmd: Option<Sub>,
}
#[test]
fn test_no_cmd() {
let result = Opt::try_parse_from(&["test"]);
assert!(result.is_err());
assert_eq!(
Opt2 {
force: false,
verbose: 0,
cmd: None
},
Opt2::try_parse_from(&["test"]).unwrap()
);
}
#[test]
fn test_fetch() {
assert_eq!(
Opt {
force: false,
verbose: 3,
cmd: Sub::Fetch {}
},
Opt::try_parse_from(&["test", "-vvv", "fetch"]).unwrap()
);
assert_eq!(
Opt {
force: true,
verbose: 0,
cmd: Sub::Fetch {}
},
Opt::try_parse_from(&["test", "--force", "fetch"]).unwrap()
);
}
#[test]
fn test_add() {
assert_eq!(
Opt {
force: false,
verbose: 0,
cmd: Sub::Add {}
},
Opt::try_parse_from(&["test", "add"]).unwrap()
);
assert_eq!(
Opt {
force: false,
verbose: 2,
cmd: Sub::Add {}
},
Opt::try_parse_from(&["test", "-vv", "add"]).unwrap()
);
}
#[test]
fn test_badinput() {
let result = Opt::try_parse_from(&["test", "badcmd"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "add", "--verbose"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "--badopt", "add"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "add", "--badopt"]);
assert!(result.is_err());
}
#[derive(Parser, PartialEq, Debug)]
struct Opt3 {
#[clap(short, long)]
all: bool,
#[clap(subcommand)]
cmd: Sub2,
}
#[derive(Subcommand, PartialEq, Debug)]
enum Sub2 {
Foo {
file: String,
#[clap(subcommand)]
cmd: Sub3,
},
Bar {},
}
#[derive(Subcommand, PartialEq, Debug)]
enum Sub3 {
Baz {},
Quux {},
}
#[test]
fn test_subsubcommand() {
assert_eq!(
Opt3 {
all: true,
cmd: Sub2::Foo {
file: "lib.rs".to_string(),
cmd: Sub3::Quux {}
}
},
Opt3::try_parse_from(&["test", "--all", "foo", "lib.rs", "quux"]).unwrap()
);
}
#[derive(Parser, PartialEq, Debug)]
enum SubSubCmdWithOption {
Remote {
#[clap(subcommand)]
cmd: Option<Remote>,
},
Stash {
#[clap(subcommand)]
cmd: Stash,
},
}
#[derive(Subcommand, PartialEq, Debug)]
enum Remote {
Add { name: String, url: String },
Remove { name: String },
}
#[derive(Subcommand, PartialEq, Debug)]
enum Stash {
Save,
Pop,
}
#[test]
fn sub_sub_cmd_with_option() {
fn make(args: &[&str]) -> Option<SubSubCmdWithOption> {
SubSubCmdWithOption::try_parse_from(args).ok()
}
assert_eq!(
Some(SubSubCmdWithOption::Remote { cmd: None }),
make(&["", "remote"])
);
assert_eq!(
Some(SubSubCmdWithOption::Remote {
cmd: Some(Remote::Add {
name: "origin".into(),
url: "http".into()
})
}),
make(&["", "remote", "add", "origin", "http"])
);
assert_eq!(
Some(SubSubCmdWithOption::Stash { cmd: Stash::Save }),
make(&["", "stash", "save"])
);
assert_eq!(None, make(&["", "stash"]));
}

View file

@ -0,0 +1,156 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::{ErrorKind, Parser};
use std::num::ParseIntError;
pub const DISPLAY_ORDER: usize = 2;
// Check if the global settings compile
#[derive(Parser, Debug, PartialEq, Eq)]
#[clap(allow_hyphen_values = true)]
struct Opt {
#[clap(
long = "x",
display_order = DISPLAY_ORDER,
next_line_help = true,
default_value = "0",
require_equals = true
)]
x: i32,
#[clap(short = 'l', long = "level", aliases = &["set-level", "lvl"])]
level: String,
#[clap(long("values"))]
values: Vec<i32>,
#[clap(name = "FILE", requires_if("FILE", "values"))]
files: Vec<String>,
}
#[test]
fn test_slice() {
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::try_parse_from(&["test", "-l", "1"]).unwrap()
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::try_parse_from(&["test", "--level", "1"]).unwrap()
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::try_parse_from(&["test", "--set-level", "1"]).unwrap()
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::try_parse_from(&["test", "--lvl", "1"]).unwrap()
);
}
#[test]
fn test_multi_args() {
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: vec!["file".to_string()],
values: vec![],
},
Opt::try_parse_from(&["test", "-l", "1", "file"]).unwrap()
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: vec!["FILE".to_string()],
values: vec![1],
},
Opt::try_parse_from(&["test", "-l", "1", "--values", "1", "--", "FILE"]).unwrap()
);
}
#[test]
fn test_multi_args_fail() {
let result = Opt::try_parse_from(&["test", "-l", "1", "--", "FILE"]);
assert!(result.is_err());
}
#[test]
fn test_bool() {
assert_eq!(
Opt {
x: 1,
level: "1".to_string(),
files: vec![],
values: vec![],
},
Opt::try_parse_from(&["test", "-l", "1", "--x=1"]).unwrap()
);
let result = Opt::try_parse_from(&["test", "-l", "1", "--x", "1"]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::NoEquals);
}
fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(input, 16)
}
#[derive(Parser, PartialEq, Debug)]
struct HexOpt {
#[clap(short, parse(try_from_str = parse_hex))]
number: u64,
}
#[test]
fn test_parse_hex_function_path() {
assert_eq!(
HexOpt { number: 5 },
HexOpt::try_parse_from(&["test", "-n", "5"]).unwrap()
);
assert_eq!(
HexOpt {
number: 0x00ab_cdef
},
HexOpt::try_parse_from(&["test", "-n", "abcdef"]).unwrap()
);
let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err();
assert!(
err.to_string().contains("invalid digit found in string"),
"{}",
err
);
}

View file

@ -0,0 +1,422 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#![allow(clippy::option_option)]
use crate::utils;
use clap::{Parser, Subcommand};
#[test]
fn required_option() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
arg: i32,
}
assert_eq!(
Opt { arg: 42 },
Opt::try_parse_from(&["test", "-a42"]).unwrap()
);
assert_eq!(
Opt { arg: 42 },
Opt::try_parse_from(&["test", "-a", "42"]).unwrap()
);
assert_eq!(
Opt { arg: 42 },
Opt::try_parse_from(&["test", "--arg", "42"]).unwrap()
);
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn option_with_default() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, default_value = "42")]
arg: i32,
}
assert_eq!(
Opt { arg: 24 },
Opt::try_parse_from(&["test", "-a24"]).unwrap()
);
assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn option_with_raw_default() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, default_value = "42")]
arg: i32,
}
assert_eq!(
Opt { arg: 24 },
Opt::try_parse_from(&["test", "-a24"]).unwrap()
);
assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn option_from_str() {
#[derive(Debug, PartialEq)]
struct A;
impl<'a> From<&'a str> for A {
fn from(_: &str) -> A {
A
}
}
#[derive(Debug, Parser, PartialEq)]
struct Opt {
#[clap(parse(from_str))]
a: Option<A>,
}
assert_eq!(Opt { a: None }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(
Opt { a: Some(A) },
Opt::try_parse_from(&["test", "foo"]).unwrap()
);
}
#[test]
fn option_type_is_optional() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<i32>,
}
assert_eq!(
Opt { arg: Some(42) },
Opt::try_parse_from(&["test", "-a42"]).unwrap()
);
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn required_with_option_type() {
#[derive(Debug, PartialEq, Eq, Parser)]
#[clap(subcommand_negates_reqs = true)]
struct Opt {
#[clap(required = true)]
req_str: Option<String>,
#[clap(subcommand)]
cmd: Option<SubCommands>,
}
#[derive(Debug, PartialEq, Eq, Subcommand)]
enum SubCommands {
ExSub {
#[clap(short, long, parse(from_occurrences))]
verbose: u8,
},
}
assert_eq!(
Opt {
req_str: Some(("arg").into()),
cmd: None,
},
Opt::try_parse_from(&["test", "arg"]).unwrap()
);
assert_eq!(
Opt {
req_str: None,
cmd: Some(SubCommands::ExSub { verbose: 1 }),
},
Opt::try_parse_from(&["test", "ex-sub", "-v"]).unwrap()
);
assert!(Opt::try_parse_from(&["test"]).is_err());
}
#[test]
fn ignore_qualified_option_type() {
fn parser(s: &str) -> Option<String> {
Some(s.to_string())
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(parse(from_str = parser))]
arg: ::std::option::Option<String>,
}
assert_eq!(
Opt {
arg: Some("success".into())
},
Opt::try_parse_from(&["test", "success"]).unwrap()
);
}
#[test]
fn option_option_type_is_optional_value() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, multiple_occurrences(true))]
#[allow(clippy::option_option)]
arg: Option<Option<i32>>,
}
assert_eq!(
Opt {
arg: Some(Some(42))
},
Opt::try_parse_from(&["test", "-a42"]).unwrap()
);
assert_eq!(
Opt { arg: Some(None) },
Opt::try_parse_from(&["test", "-a"]).unwrap()
);
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn option_option_type_help() {
#[derive(Parser, Debug)]
struct Opt {
#[clap(long, value_name = "val")]
arg: Option<Option<i32>>,
}
let help = utils::get_help::<Opt>();
assert!(help.contains("--arg [<val>]"));
assert!(!help.contains("--arg [<val>]..."));
}
#[test]
fn two_option_option_types() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Option<i32>>,
#[clap(long)]
field: Option<Option<String>>,
}
assert_eq!(
Opt {
arg: Some(Some(42)),
field: Some(Some("f".into()))
},
Opt::try_parse_from(&["test", "-a42", "--field", "f"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(Some(42)),
field: Some(None)
},
Opt::try_parse_from(&["test", "-a42", "--field"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(None),
field: Some(None)
},
Opt::try_parse_from(&["test", "-a", "--field"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(None),
field: Some(Some("f".into()))
},
Opt::try_parse_from(&["test", "-a", "--field", "f"]).unwrap()
);
assert_eq!(
Opt {
arg: None,
field: Some(None)
},
Opt::try_parse_from(&["test", "--field"]).unwrap()
);
assert_eq!(
Opt {
arg: None,
field: None
},
Opt::try_parse_from(&["test"]).unwrap()
);
}
#[test]
fn vec_type_is_multiple_occurrences() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
arg: Vec<i32>,
}
assert_eq!(
Opt { arg: vec![24] },
Opt::try_parse_from(&["test", "-a24"]).unwrap()
);
assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
);
}
#[test]
fn vec_type_with_required() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long, required = true)]
arg: Vec<i32>,
}
assert_eq!(
Opt { arg: vec![24] },
Opt::try_parse_from(&["test", "-a24"]).unwrap()
);
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
);
}
#[test]
fn vec_type_with_multiple_values_only() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long, multiple_values(true), multiple_occurrences(false))]
arg: Vec<i32>,
}
assert_eq!(
Opt { arg: vec![24] },
Opt::try_parse_from(&["test", "-a24"]).unwrap()
);
assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::try_parse_from(&["test", "-a", "24", "42"]).unwrap()
);
}
#[test]
fn ignore_qualified_vec_type() {
fn parser(s: &str) -> Vec<String> {
vec![s.to_string()]
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(parse(from_str = parser))]
arg: ::std::vec::Vec<String>,
}
assert_eq!(
Opt {
arg: vec!["success".into()]
},
Opt::try_parse_from(&["test", "success"]).unwrap()
);
}
#[test]
fn option_vec_type() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Vec<i32>>,
}
assert_eq!(
Opt { arg: Some(vec![1]) },
Opt::try_parse_from(&["test", "-a", "1"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::try_parse_from(&["test", "-a", "1", "-a", "2"]).unwrap()
);
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
}
#[test]
fn option_vec_type_structopt_behavior() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short, long, multiple_values(true), min_values(0))]
arg: Option<Vec<i32>>,
}
assert_eq!(
Opt { arg: Some(vec![1]) },
Opt::try_parse_from(&["test", "-a", "1"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::try_parse_from(&["test", "-a", "1", "2"]).unwrap()
);
assert_eq!(
Opt { arg: Some(vec![]) },
Opt::try_parse_from(&["test", "-a"]).unwrap()
);
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
}
#[test]
fn two_option_vec_types() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Vec<i32>>,
#[clap(short)]
b: Option<Vec<i32>>,
}
assert_eq!(
Opt {
arg: Some(vec![1]),
b: None,
},
Opt::try_parse_from(&["test", "-a", "1"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(vec![1]),
b: Some(vec![1])
},
Opt::try_parse_from(&["test", "-a", "1", "-b", "1"]).unwrap()
);
assert_eq!(
Opt {
arg: Some(vec![1, 2]),
b: Some(vec![1, 2])
},
Opt::try_parse_from(&["test", "-a", "1", "-a", "2", "-b", "1", "-b", "2"]).unwrap()
);
assert_eq!(
Opt { arg: None, b: None },
Opt::try_parse_from(&["test"]).unwrap()
);
}

View file

@ -0,0 +1,36 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
mod options {
use clap::Parser;
#[derive(Debug, Parser)]
pub struct Options {
#[clap(subcommand)]
pub subcommand: super::subcommands::SubCommand,
}
}
mod subcommands {
use clap::Subcommand;
#[derive(Debug, Subcommand)]
pub enum SubCommand {
/// foo
Foo {
/// foo
bars: String,
},
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Parser;
#[test]
fn raw_bool_literal() {
#[derive(Parser, Debug, PartialEq)]
#[clap(name = "raw_bool")]
struct Opt {
#[clap(raw(false))]
a: String,
#[clap(raw(true))]
b: String,
}
assert_eq!(
Opt {
a: "one".into(),
b: "--help".into()
},
Opt::try_parse_from(&["test", "one", "--", "--help"]).unwrap()
);
}

View file

@ -0,0 +1,24 @@
use clap::Parser;
#[test]
fn raw_idents() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(short, long)]
r#type: String,
}
assert_eq!(
Opt {
r#type: "long".into()
},
Opt::try_parse_from(&["test", "--type", "long"]).unwrap()
);
assert_eq!(
Opt {
r#type: "short".into()
},
Opt::try_parse_from(&["test", "-t", "short"]).unwrap()
);
}

View file

@ -0,0 +1,47 @@
#![cfg(feature = "env")]
use crate::utils;
use clap::Parser;
#[test]
fn it_works() {
#[derive(Debug, PartialEq, Parser)]
#[clap(rename_all_env = "kebab")]
struct BehaviorModel {
#[clap(env)]
be_nice: String,
}
let help = utils::get_help::<BehaviorModel>();
assert!(help.contains("[env: be-nice=]"));
}
#[test]
fn default_is_screaming() {
#[derive(Debug, PartialEq, Parser)]
struct BehaviorModel {
#[clap(env)]
be_nice: String,
}
let help = utils::get_help::<BehaviorModel>();
assert!(help.contains("[env: BE_NICE=]"));
}
#[test]
fn overridable() {
#[derive(Debug, PartialEq, Parser)]
#[clap(rename_all_env = "kebab")]
struct BehaviorModel {
#[clap(env)]
be_nice: String,
#[clap(rename_all_env = "pascal", env)]
be_aggressive: String,
}
let help = utils::get_help::<BehaviorModel>();
assert!(help.contains("[env: be-nice=]"));
assert!(help.contains("[env: BeAggressive=]"));
}

155
tests/derive/legacy/skip.rs Normal file
View file

@ -0,0 +1,155 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Parser;
#[test]
fn skip_1() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(short)]
x: u32,
#[clap(skip)]
s: u32,
}
assert!(Opt::try_parse_from(&["test", "-x", "10", "20"]).is_err());
let mut opt = Opt::try_parse_from(&["test", "-x", "10"]).unwrap();
assert_eq!(
opt,
Opt {
x: 10,
s: 0, // default
}
);
opt.s = 42;
opt.update_from(&["test", "-x", "22"]);
assert_eq!(opt, Opt { x: 22, s: 42 });
}
#[test]
fn skip_2() {
#[derive(Parser, Debug, PartialEq)]
struct Opt {
#[clap(short)]
x: u32,
#[clap(skip)]
ss: String,
#[clap(skip)]
sn: u8,
y: u32,
#[clap(skip)]
sz: u16,
t: u32,
}
assert_eq!(
Opt::try_parse_from(&["test", "-x", "10", "20", "30"]).unwrap(),
Opt {
x: 10,
ss: String::from(""),
sn: 0,
y: 20,
sz: 0,
t: 30,
}
);
}
#[test]
fn skip_enum() {
#[derive(Debug, PartialEq)]
#[allow(unused)]
enum Kind {
A,
B,
}
impl Default for Kind {
fn default() -> Self {
Kind::B
}
}
#[derive(Parser, Debug, PartialEq)]
pub struct Opt {
#[clap(long, short)]
number: u32,
#[clap(skip)]
k: Kind,
#[clap(skip)]
v: Vec<u32>,
}
assert_eq!(
Opt::try_parse_from(&["test", "-n", "10"]).unwrap(),
Opt {
number: 10,
k: Kind::B,
v: vec![],
}
);
}
#[test]
fn skip_help_doc_comments() {
#[derive(Parser, Debug, PartialEq)]
pub struct Opt {
#[clap(skip, help = "internal_stuff")]
a: u32,
#[clap(skip, long_help = "internal_stuff\ndo not touch")]
b: u32,
/// Not meant to be used by clap.
///
/// I want a default here.
#[clap(skip)]
c: u32,
#[clap(short, parse(try_from_str))]
n: u32,
}
assert_eq!(
Opt::try_parse_from(&["test", "-n", "10"]).unwrap(),
Opt {
n: 10,
a: 0,
b: 0,
c: 0,
}
);
}
#[test]
fn skip_val() {
#[derive(Parser, Debug, PartialEq)]
pub struct Opt {
#[clap(long, short)]
number: u32,
#[clap(skip = "key")]
k: String,
#[clap(skip = vec![1, 2, 3])]
v: Vec<u32>,
}
assert_eq!(
Opt::try_parse_from(&["test", "-n", "10"]).unwrap(),
Opt {
number: 10,
k: "key".to_string(),
v: vec![1, 2, 3]
}
);
}

View file

@ -0,0 +1,23 @@
#![allow(deprecated)]
use clap::{AppSettings, StructOpt};
#[test]
fn compatible() {
#[derive(StructOpt)]
#[structopt(author, version, about)]
#[structopt(global_setting(AppSettings::PropagateVersion))]
struct Cli {
#[structopt(subcommand)]
command: Commands,
}
#[derive(StructOpt)]
#[structopt(setting(AppSettings::SubcommandRequiredElseHelp))]
enum Commands {
/// Adds files to myapp
Add { name: Option<String> },
}
Cli::from_iter(["test", "add"]);
}

View file

@ -0,0 +1,605 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use crate::utils;
use clap::{Args, Parser, Subcommand};
#[derive(Parser, PartialEq, Debug)]
enum Opt {
/// Fetch stuff from GitHub
Fetch {
#[clap(long)]
all: bool,
#[clap(short, long)]
/// Overwrite local branches.
force: bool,
repo: String,
},
Add {
#[clap(short, long)]
interactive: bool,
#[clap(short, long)]
verbose: bool,
},
}
#[test]
fn test_fetch() {
assert_eq!(
Opt::Fetch {
all: true,
force: false,
repo: "origin".to_string()
},
Opt::try_parse_from(&["test", "fetch", "--all", "origin"]).unwrap()
);
assert_eq!(
Opt::Fetch {
all: false,
force: true,
repo: "origin".to_string()
},
Opt::try_parse_from(&["test", "fetch", "-f", "origin"]).unwrap()
);
}
#[test]
fn test_add() {
assert_eq!(
Opt::Add {
interactive: false,
verbose: false
},
Opt::try_parse_from(&["test", "add"]).unwrap()
);
assert_eq!(
Opt::Add {
interactive: true,
verbose: true
},
Opt::try_parse_from(&["test", "add", "-i", "-v"]).unwrap()
);
}
#[test]
fn test_no_parse() {
let result = Opt::try_parse_from(&["test", "badcmd", "-i", "-v"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "add", "--badoption"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test"]);
assert!(result.is_err());
}
#[derive(Parser, PartialEq, Debug)]
enum Opt2 {
DoSomething { arg: String },
}
#[test]
/// This test is specifically to make sure that hyphenated subcommands get
/// processed correctly.
fn test_hyphenated_subcommands() {
assert_eq!(
Opt2::DoSomething {
arg: "blah".to_string()
},
Opt2::try_parse_from(&["test", "do-something", "blah"]).unwrap()
);
}
#[derive(Parser, PartialEq, Debug)]
enum Opt3 {
Add,
Init,
Fetch,
}
#[test]
fn test_null_commands() {
assert_eq!(Opt3::Add, Opt3::try_parse_from(&["test", "add"]).unwrap());
assert_eq!(Opt3::Init, Opt3::try_parse_from(&["test", "init"]).unwrap());
assert_eq!(
Opt3::Fetch,
Opt3::try_parse_from(&["test", "fetch"]).unwrap()
);
}
#[derive(Parser, PartialEq, Debug)]
#[clap(about = "Not shown")]
struct Add {
file: String,
}
/// Not shown
#[derive(Parser, PartialEq, Debug)]
struct Fetch {
remote: String,
}
#[derive(Parser, PartialEq, Debug)]
enum Opt4 {
// Not shown
/// Add a file
Add(Add),
Init,
/// download history from remote
Fetch(Fetch),
}
#[test]
fn test_tuple_commands() {
assert_eq!(
Opt4::Add(Add {
file: "f".to_string()
}),
Opt4::try_parse_from(&["test", "add", "f"]).unwrap()
);
assert_eq!(Opt4::Init, Opt4::try_parse_from(&["test", "init"]).unwrap());
assert_eq!(
Opt4::Fetch(Fetch {
remote: "origin".to_string()
}),
Opt4::try_parse_from(&["test", "fetch", "origin"]).unwrap()
);
let output = utils::get_long_help::<Opt4>();
assert!(output.contains("download history from remote"));
assert!(output.contains("Add a file"));
assert!(!output.contains("Not shown"));
}
#[test]
fn global_passed_down() {
#[derive(Debug, PartialEq, Parser)]
struct Opt {
#[clap(global = true, long)]
other: bool,
#[clap(subcommand)]
sub: Subcommands,
}
#[derive(Debug, PartialEq, Subcommand)]
enum Subcommands {
Add,
Global(GlobalCmd),
}
#[derive(Debug, PartialEq, Args)]
struct GlobalCmd {
#[clap(from_global)]
other: bool,
}
assert_eq!(
Opt::try_parse_from(&["test", "global"]).unwrap(),
Opt {
other: false,
sub: Subcommands::Global(GlobalCmd { other: false })
}
);
assert_eq!(
Opt::try_parse_from(&["test", "global", "--other"]).unwrap(),
Opt {
other: true,
sub: Subcommands::Global(GlobalCmd { other: true })
}
);
}
#[test]
fn external_subcommand() {
#[derive(Debug, PartialEq, Parser)]
struct Opt {
#[clap(subcommand)]
sub: Subcommands,
}
#[derive(Debug, PartialEq, Subcommand)]
enum Subcommands {
Add,
Remove,
#[clap(external_subcommand)]
Other(Vec<String>),
}
assert_eq!(
Opt::try_parse_from(&["test", "add"]).unwrap(),
Opt {
sub: Subcommands::Add
}
);
assert_eq!(
Opt::try_parse_from(&["test", "remove"]).unwrap(),
Opt {
sub: Subcommands::Remove
}
);
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt::try_parse_from(&["test", "git", "status"]).unwrap(),
Opt {
sub: Subcommands::Other(vec!["git".into(), "status".into()])
}
);
}
#[test]
fn external_subcommand_os_string() {
use std::ffi::OsString;
#[derive(Debug, PartialEq, Parser)]
struct Opt {
#[clap(subcommand)]
sub: Subcommands,
}
#[derive(Debug, PartialEq, Subcommand)]
enum Subcommands {
#[clap(external_subcommand)]
Other(Vec<OsString>),
}
assert_eq!(
Opt::try_parse_from(&["test", "git", "status"]).unwrap(),
Opt {
sub: Subcommands::Other(vec!["git".into(), "status".into()])
}
);
assert!(Opt::try_parse_from(&["test"]).is_err());
}
#[test]
fn external_subcommand_optional() {
#[derive(Debug, PartialEq, Parser)]
struct Opt {
#[clap(subcommand)]
sub: Option<Subcommands>,
}
#[derive(Debug, PartialEq, Subcommand)]
enum Subcommands {
#[clap(external_subcommand)]
Other(Vec<String>),
}
assert_eq!(
Opt::try_parse_from(&["test", "git", "status"]).unwrap(),
Opt {
sub: Some(Subcommands::Other(vec!["git".into(), "status".into()]))
}
);
assert_eq!(Opt::try_parse_from(&["test"]).unwrap(), Opt { sub: None });
}
#[test]
fn enum_in_enum_subsubcommand() {
#[derive(Parser, Debug, PartialEq)]
pub enum Opt {
#[clap(alias = "l")]
List,
#[clap(subcommand, alias = "d")]
Daemon(DaemonCommand),
}
#[derive(Subcommand, Debug, PartialEq)]
pub enum DaemonCommand {
Start,
Stop,
}
let result = Opt::try_parse_from(&["test"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "list"]).unwrap();
assert_eq!(Opt::List, result);
let result = Opt::try_parse_from(&["test", "l"]).unwrap();
assert_eq!(Opt::List, result);
let result = Opt::try_parse_from(&["test", "daemon"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "daemon", "start"]).unwrap();
assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
let result = Opt::try_parse_from(&["test", "d", "start"]).unwrap();
assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
}
#[test]
fn update_subcommands() {
#[derive(Parser, PartialEq, Debug)]
enum Opt {
Command1(Command1),
Command2(Command2),
}
#[derive(Parser, PartialEq, Debug)]
struct Command1 {
arg1: i32,
arg2: i32,
}
#[derive(Parser, PartialEq, Debug)]
struct Command2 {
arg2: i32,
}
// Full subcommand update
let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
opt.try_update_from(&["test", "command1", "42", "44"])
.unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "command1", "42", "44"]).unwrap(),
opt
);
// Partial subcommand update
let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
opt.try_update_from(&["test", "command1", "42"]).unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "command1", "42", "14"]).unwrap(),
opt
);
// Change subcommand
let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
opt.try_update_from(&["test", "command2", "43"]).unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "command2", "43"]).unwrap(),
opt
);
}
#[test]
fn update_sub_subcommands() {
#[derive(Parser, PartialEq, Debug)]
enum Opt {
#[clap(subcommand)]
Child1(Child1),
#[clap(subcommand)]
Child2(Child2),
}
#[derive(Subcommand, PartialEq, Debug)]
enum Child1 {
Command1(Command1),
Command2(Command2),
}
#[derive(Subcommand, PartialEq, Debug)]
enum Child2 {
Command1(Command1),
Command2(Command2),
}
#[derive(Args, PartialEq, Debug)]
struct Command1 {
arg1: i32,
arg2: i32,
}
#[derive(Args, PartialEq, Debug)]
struct Command2 {
arg2: i32,
}
// Full subcommand update
let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
opt.try_update_from(&["test", "child1", "command1", "42", "44"])
.unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "child1", "command1", "42", "44"]).unwrap(),
opt
);
// Partial subcommand update
let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
opt.try_update_from(&["test", "child1", "command1", "42"])
.unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "child1", "command1", "42", "14"]).unwrap(),
opt
);
// Partial subcommand update
let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
opt.try_update_from(&["test", "child1", "command2", "43"])
.unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "child1", "command2", "43"]).unwrap(),
opt
);
// Change subcommand
let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
opt.try_update_from(&["test", "child2", "command2", "43"])
.unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "child2", "command2", "43"]).unwrap(),
opt
);
}
#[test]
fn update_ext_subcommand() {
#[derive(Parser, PartialEq, Debug)]
enum Opt {
Command1(Command1),
Command2(Command2),
#[clap(external_subcommand)]
Ext(Vec<String>),
}
#[derive(Args, PartialEq, Debug)]
struct Command1 {
arg1: i32,
arg2: i32,
}
#[derive(Args, PartialEq, Debug)]
struct Command2 {
arg2: i32,
}
// Full subcommand update
let mut opt = Opt::Ext(vec!["12".into(), "14".into()]);
opt.try_update_from(&["test", "ext", "42", "44"]).unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "ext", "42", "44"]).unwrap(),
opt
);
// No partial subcommand update
let mut opt = Opt::Ext(vec!["12".into(), "14".into()]);
opt.try_update_from(&["test", "ext", "42"]).unwrap();
assert_eq!(Opt::try_parse_from(&["test", "ext", "42"]).unwrap(), opt);
// Change subcommand
let mut opt = Opt::Ext(vec!["12".into(), "14".into()]);
opt.try_update_from(&["test", "command2", "43"]).unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "command2", "43"]).unwrap(),
opt
);
let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
opt.try_update_from(&["test", "ext", "42", "44"]).unwrap();
assert_eq!(
Opt::try_parse_from(&["test", "ext", "42", "44"]).unwrap(),
opt
);
}
#[test]
fn subcommand_name_not_literal() {
fn get_name() -> &'static str {
"renamed"
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(subcommand)]
subcmd: SubCmd,
}
#[derive(Subcommand, PartialEq, Debug)]
enum SubCmd {
#[clap(name = get_name())]
SubCmd1,
}
assert!(Opt::try_parse_from(&["test", "renamed"]).is_ok());
}
#[test]
fn skip_subcommand() {
#[derive(Debug, PartialEq, Parser)]
struct Opt {
#[clap(subcommand)]
sub: Subcommands,
}
#[derive(Debug, PartialEq, Subcommand)]
enum Subcommands {
Add,
Remove,
#[allow(dead_code)]
#[clap(skip)]
Skip,
}
assert_eq!(
Opt::try_parse_from(&["test", "add"]).unwrap(),
Opt {
sub: Subcommands::Add
}
);
assert_eq!(
Opt::try_parse_from(&["test", "remove"]).unwrap(),
Opt {
sub: Subcommands::Remove
}
);
let res = Opt::try_parse_from(&["test", "skip"]);
assert_eq!(res.unwrap_err().kind(), clap::ErrorKind::UnknownArgument,);
}
#[test]
#[cfg(feature = "unstable-v4")]
fn built_in_subcommand_escaped() {
#[derive(Debug, PartialEq, Parser)]
enum Command {
Install {
arg: Option<String>,
},
#[clap(external_subcommand)]
Custom(Vec<String>),
}
assert_eq!(
Command::try_parse_from(&["test", "install", "arg"]).unwrap(),
Command::Install {
arg: Some(String::from("arg"))
}
);
assert_eq!(
Command::try_parse_from(&["test", "--", "install"]).unwrap(),
Command::Custom(vec![String::from("install")])
);
assert_eq!(
Command::try_parse_from(&["test", "--", "install", "arg"]).unwrap(),
Command::Custom(vec![String::from("install"), String::from("arg")])
);
}
#[test]
#[cfg(not(feature = "unstable-v4"))]
fn built_in_subcommand_escaped() {
#[derive(Debug, PartialEq, Parser)]
enum Command {
Install {
arg: Option<String>,
},
#[clap(external_subcommand)]
Custom(Vec<String>),
}
assert_eq!(
Command::try_parse_from(&["test", "install", "arg"]).unwrap(),
Command::Install {
arg: Some(String::from("arg"))
}
);
assert_eq!(
Command::try_parse_from(&["test", "--", "install"]).unwrap(),
Command::Install { arg: None }
);
assert_eq!(
Command::try_parse_from(&["test", "--", "install", "arg"]).unwrap(),
Command::Install { arg: None }
);
}

View file

@ -0,0 +1,47 @@
/// Regression test to ensure that type aliases do not cause compilation failures.
use std::str::FromStr;
use clap::{ArgEnum, Parser, Subcommand};
// Result type alias
#[allow(dead_code)]
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
// Wrapper to use for Option type alias
#[derive(Debug, PartialEq, Eq)]
struct Wrapper<T>(T);
impl<T: FromStr> FromStr for Wrapper<T> {
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
T::from_str(s).map(Wrapper)
}
}
type Option<T> = std::option::Option<Wrapper<T>>;
#[derive(Parser)]
pub struct Opts {
another_string: String,
#[clap(subcommand)]
command: Command,
#[clap(short, long, arg_enum)]
choice: ArgChoice,
}
#[derive(Subcommand, PartialEq, Debug)]
enum Command {
DoSomething { arg: Option<String> },
}
#[derive(ArgEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[test]
fn type_alias_regressions() {
Opts::try_parse_from(["test", "value", "--choice=foo", "do-something"]).unwrap();
}

226
tests/derive/legacy/utf8.rs Normal file
View file

@ -0,0 +1,226 @@
#![cfg(not(windows))]
use clap::{ErrorKind, Parser};
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
#[derive(Parser, Debug, PartialEq, Eq)]
struct Positional {
arg: String,
}
#[derive(Parser, Debug, PartialEq, Eq)]
struct Named {
#[clap(short, long)]
arg: String,
}
#[test]
fn invalid_utf8_strict_positional() {
let m = Positional::try_parse_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[test]
fn invalid_utf8_strict_option_short_space() {
let m = Named::try_parse_from(vec![
OsString::from(""),
OsString::from("-a"),
OsString::from_vec(vec![0xe9]),
]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[test]
fn invalid_utf8_strict_option_short_equals() {
let m = Named::try_parse_from(vec![
OsString::from(""),
OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]),
]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[test]
fn invalid_utf8_strict_option_short_no_space() {
let m = Named::try_parse_from(vec![
OsString::from(""),
OsString::from_vec(vec![0x2d, 0x61, 0xe9]),
]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[test]
fn invalid_utf8_strict_option_long_space() {
let m = Named::try_parse_from(vec![
OsString::from(""),
OsString::from("--arg"),
OsString::from_vec(vec![0xe9]),
]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[test]
fn invalid_utf8_strict_option_long_equals() {
let m = Named::try_parse_from(vec![
OsString::from(""),
OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]),
]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[derive(Parser, Debug, PartialEq, Eq)]
struct PositionalOs {
#[clap(parse(from_os_str))]
arg: OsString,
}
#[derive(Parser, Debug, PartialEq, Eq)]
struct NamedOs {
#[clap(short, long, parse(from_os_str))]
arg: OsString,
}
#[test]
fn invalid_utf8_positional() {
let r = PositionalOs::try_parse_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]);
assert_eq!(
r.unwrap(),
PositionalOs {
arg: OsString::from_vec(vec![0xe9])
}
);
}
#[test]
fn invalid_utf8_option_short_space() {
let r = NamedOs::try_parse_from(vec![
OsString::from(""),
OsString::from("-a"),
OsString::from_vec(vec![0xe9]),
]);
assert_eq!(
r.unwrap(),
NamedOs {
arg: OsString::from_vec(vec![0xe9])
}
);
}
#[test]
fn invalid_utf8_option_short_equals() {
let r = NamedOs::try_parse_from(vec![
OsString::from(""),
OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]),
]);
assert_eq!(
r.unwrap(),
NamedOs {
arg: OsString::from_vec(vec![0xe9])
}
);
}
#[test]
fn invalid_utf8_option_short_no_space() {
let r = NamedOs::try_parse_from(vec![
OsString::from(""),
OsString::from_vec(vec![0x2d, 0x61, 0xe9]),
]);
assert_eq!(
r.unwrap(),
NamedOs {
arg: OsString::from_vec(vec![0xe9])
}
);
}
#[test]
fn invalid_utf8_option_long_space() {
let r = NamedOs::try_parse_from(vec![
OsString::from(""),
OsString::from("--arg"),
OsString::from_vec(vec![0xe9]),
]);
assert_eq!(
r.unwrap(),
NamedOs {
arg: OsString::from_vec(vec![0xe9])
}
);
}
#[test]
fn invalid_utf8_option_long_equals() {
let r = NamedOs::try_parse_from(vec![
OsString::from(""),
OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]),
]);
assert_eq!(
r.unwrap(),
NamedOs {
arg: OsString::from_vec(vec![0xe9])
}
);
}
#[derive(Debug, PartialEq, Parser)]
enum External {
#[clap(external_subcommand)]
Other(Vec<String>),
}
#[test]
fn refuse_invalid_utf8_subcommand_with_allow_external_subcommands() {
let m = External::try_parse_from(vec![
OsString::from(""),
OsString::from_vec(vec![0xe9]),
OsString::from("normal"),
]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[test]
fn refuse_invalid_utf8_subcommand_args_with_allow_external_subcommands() {
let m = External::try_parse_from(vec![
OsString::from(""),
OsString::from("subcommand"),
OsString::from("normal"),
OsString::from_vec(vec![0xe9]),
OsString::from("--another_normal"),
]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8);
}
#[derive(Debug, PartialEq, Parser)]
enum ExternalOs {
#[clap(external_subcommand)]
Other(Vec<OsString>),
}
#[test]
fn allow_invalid_utf8_subcommand_args_with_allow_external_subcommands() {
let m = ExternalOs::try_parse_from(vec![
OsString::from(""),
OsString::from("subcommand"),
OsString::from("normal"),
OsString::from_vec(vec![0xe9]),
OsString::from("--another_normal"),
]);
assert_eq!(
m.unwrap(),
ExternalOs::Other(vec![
OsString::from("subcommand"),
OsString::from("normal"),
OsString::from_vec(vec![0xe9]),
OsString::from("--another_normal"),
])
);
}

View file

@ -0,0 +1,56 @@
// Hi, future me (or whoever you are)!
//
// Yes, we do need this attr.
// No, the warnings cannot be fixed otherwise.
// Accept and endure. Do not touch.
#![allow(unused)]
use clap::CommandFactory;
pub fn get_help<T: CommandFactory>() -> String {
let mut output = Vec::new();
<T as CommandFactory>::command()
.write_help(&mut output)
.unwrap();
let output = String::from_utf8(output).unwrap();
eprintln!("\n%%% HELP %%%:=====\n{}\n=====\n", output);
eprintln!("\n%%% HELP (DEBUG) %%%:=====\n{:?}\n=====\n", output);
output
}
pub fn get_long_help<T: CommandFactory>() -> String {
let mut output = Vec::new();
<T as CommandFactory>::command()
.write_long_help(&mut output)
.unwrap();
let output = String::from_utf8(output).unwrap();
eprintln!("\n%%% LONG_HELP %%%:=====\n{}\n=====\n", output);
eprintln!("\n%%% LONG_HELP (DEBUG) %%%:=====\n{:?}\n=====\n", output);
output
}
pub fn get_subcommand_long_help<T: CommandFactory>(subcmd: &str) -> String {
let mut output = Vec::new();
<T as CommandFactory>::command()
.get_subcommands_mut()
.find(|s| s.get_name() == subcmd)
.unwrap()
.write_long_help(&mut output)
.unwrap();
let output = String::from_utf8(output).unwrap();
eprintln!(
"\n%%% SUBCOMMAND `{}` HELP %%%:=====\n{}\n=====\n",
subcmd, output
);
eprintln!(
"\n%%% SUBCOMMAND `{}` HELP (DEBUG) %%%:=====\n{:?}\n=====\n",
subcmd, output
);
output
}

View file

@ -16,6 +16,7 @@ mod flatten;
mod generic;
mod help;
mod issues;
mod legacy;
mod macros;
mod naming;
mod nested_subcommands;

View file

@ -20,3 +20,48 @@ help: `bool` is a unit variant, you need to write it without the parenthesis
|
7 | opts: bool,
| ~~~~
warning: use of deprecated associated function `clap::Arg::<'help>::possible_values`: Replaced with `Arg::value_parser(PossibleValuesParser::new(...)).takes_value(true)`
--> tests/derive_ui/bool_arg_enum.rs:7:11
|
7 | opts: bool,
| ^^^^
|
= note: `#[warn(deprecated)]` on by default
error[E0277]: the trait bound `bool: ArgEnum` is not satisfied
--> tests/derive_ui/bool_arg_enum.rs:7:11
|
7 | opts: bool,
| ^^^^ the trait `ArgEnum` is not implemented for `bool`
|
note: required by `value_variants`
--> src/derive.rs
|
| fn value_variants<'a>() -> &'a [Self];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `bool: ArgEnum` is not satisfied
--> tests/derive_ui/bool_arg_enum.rs:6:5
|
6 | / #[clap(short, arg_enum)]
7 | | opts: bool,
| |______________^ the trait `ArgEnum` is not implemented for `bool`
|
note: required by a bound in `ArgEnum`
--> src/derive.rs
|
| / pub trait ArgEnum: Sized + Clone {
| | /// All possible argument values, in display order.
| | fn value_variants<'a>() -> &'a [Self];
| |
... |
| | fn to_possible_value<'a>(&self) -> Option<PossibleValue<'a>>;
| | }
| |_^ required by this bound in `ArgEnum`
warning: use of deprecated associated function `clap::Arg::<'help>::possible_values`: Replaced with `Arg::value_parser(PossibleValuesParser::new(...)).takes_value(true)`
--> tests/derive_ui/bool_arg_enum.rs:7:11
|
7 | opts: bool,
| ^^^^

View file

@ -1,4 +1,4 @@
error: `value_parse` attribute conflicts with `parse` attribute
error: `value_parser` attribute conflicts with `parse` attribute
--> tests/derive_ui/parse_with_value_parser.rs:14:29
|
14 | #[clap(parse(from_str), value_parser = clap::value_parser!(String))]