Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(derive): Switch default action/parser for unstable-v4 #3827

Merged
merged 5 commits into from Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 27 additions & 4 deletions clap_derive/src/attrs.rs
Expand Up @@ -784,9 +784,18 @@ impl Attrs {
.unwrap_or_else(|| {
if let Some(action) = self.action.as_ref() {
let inner_type = inner_type(field_type);
default_value_parser(inner_type, action.span())
} else {
let span = action.span();
default_value_parser(inner_type, span)
} else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) {
self.parser(field_type).value_parser()
} else {
let inner_type = inner_type(field_type);
let span = self
.action
.as_ref()
.map(|a| a.span())
.unwrap_or_else(|| self.kind.span());
default_value_parser(inner_type, span)
}
})
}
Expand All @@ -797,13 +806,27 @@ impl Attrs {
.map(|p| p.resolve(field_type))
.unwrap_or_else(|| {
if let Some(value_parser) = self.value_parser.as_ref() {
default_action(field_type, value_parser.span())
} else {
let span = value_parser.span();
default_action(field_type, span)
} else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) {
self.parser(field_type).action()
} else {
let span = self
.value_parser
.as_ref()
.map(|a| a.span())
.unwrap_or_else(|| self.kind.span());
default_action(field_type, span)
}
})
}

#[cfg(feature = "unstable-v4")]
pub fn ignore_parser(&self) -> bool {
self.parser.is_none()
}

#[cfg(not(feature = "unstable-v4"))]
pub fn ignore_parser(&self) -> bool {
self.value_parser.is_some() || self.action.is_some()
}
Expand Down
1 change: 1 addition & 0 deletions tests/builder/legacy/mod.rs
@@ -1,4 +1,5 @@
#![allow(deprecated)]
#![cfg(not(feature = "unstable-v4"))]

mod app_from_crate;
mod app_settings;
Expand Down
3 changes: 2 additions & 1 deletion tests/derive/flags.rs
Expand Up @@ -167,7 +167,7 @@ fn mixed_type_flags() {
fn ignore_qualified_bool_type() {
mod inner {
#[allow(non_camel_case_types)]
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Clone)]
pub struct bool(pub String);

impl std::str::FromStr for self::bool {
Expand All @@ -181,6 +181,7 @@ fn ignore_qualified_bool_type() {

#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(action)]
arg: inner::bool,
}

Expand Down
1 change: 1 addition & 0 deletions tests/derive/legacy/mod.rs
@@ -1,4 +1,5 @@
#![allow(deprecated)]
#![cfg(not(feature = "unstable-v4"))]

mod app_name;
mod arg_enum;
Expand Down
4 changes: 3 additions & 1 deletion tests/derive/main.rs
@@ -1,5 +1,8 @@
#![cfg(feature = "derive")]

mod legacy;
mod next;

mod app_name;
mod arguments;
mod author_version_about;
Expand All @@ -15,7 +18,6 @@ mod flatten;
mod generic;
mod help;
mod issues;
mod legacy;
mod macros;
mod naming;
mod nested_subcommands;
Expand Down
97 changes: 97 additions & 0 deletions tests/derive/next/app_name.rs
@@ -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"));
}
120 changes: 120 additions & 0 deletions tests/derive/next/arguments.rs
@@ -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()
);
}
51 changes: 51 additions & 0 deletions tests/derive/next/author_version_about.rs
@@ -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"));
}