Skip to content

Commit

Permalink
Merge pull request #3987 from emersonford/default-values-t-v3-backport
Browse files Browse the repository at this point in the history
feat(clap_derive): Add `default_values_t` and `default_values_os_t` (backport for Clap v3)
  • Loading branch information
epage committed Jul 25, 2022
2 parents 9262d92 + 027a675 commit 69b0904
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 1 deletion.
153 changes: 153 additions & 0 deletions clap_derive/src/attrs.rs
Expand Up @@ -564,6 +564,81 @@ impl Attrs {
self.methods.push(Method::new(raw_ident, val));
}

DefaultValuesT(ident, expr) => {
let ty = if let Some(ty) = self.ty.as_ref() {
ty
} else {
abort!(
ident,
"#[clap(default_values_t)] (without an argument) can be used \
only on field level";

note = "see \
https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
};

let container_type = Ty::from_syn_ty(ty);
if *container_type != Ty::Vec {
abort!(
ident,
"#[clap(default_values_t)] can be used only on Vec types";

note = "see \
https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
}
let inner_type = inner_type(ty);

// Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and
// `Vec<#inner_type>`.
let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) {
quote_spanned!(ident.span()=> {
{
fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<&'static str>
where
T: ::std::borrow::Borrow<#inner_type>
{
iterable
.into_iter()
.map(|val| {
clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name()
})
.collect()

}

static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
iter_to_vals(#expr)
});
&*DEFAULT_VALUES.as_slice()
}
})
} else {
quote_spanned!(ident.span()=> {
{
fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<String>
where
T: ::std::borrow::Borrow<#inner_type>
{
iterable.into_iter().map(|val| val.borrow().to_string()).collect()

}

static DEFAULT_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::string::String>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
iter_to_vals(#expr)
});

static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
DEFAULT_STRINGS.iter().map(::std::string::String::as_str).collect()
});
&*DEFAULT_VALUES.as_slice()
}
})
};

self.methods
.push(Method::new(Ident::new("default_values", ident.span()), val));
}

DefaultValueOsT(ident, expr) => {
let ty = if let Some(ty) = self.ty.as_ref() {
ty
Expand Down Expand Up @@ -604,6 +679,84 @@ impl Attrs {
self.methods.push(Method::new(raw_ident, val));
}

DefaultValuesOsT(ident, expr) => {
let ty = if let Some(ty) = self.ty.as_ref() {
ty
} else {
abort!(
ident,
"#[clap(default_values_os_t)] (without an argument) can be used \
only on field level";

note = "see \
https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
};

let container_type = Ty::from_syn_ty(ty);
if *container_type != Ty::Vec {
abort!(
ident,
"#[clap(default_values_os_t)] can be used only on Vec types";

note = "see \
https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
}
let inner_type = inner_type(ty);

// Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and
// `Vec<#inner_type>`.
let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) {
quote_spanned!(ident.span()=> {
{
fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<&'static ::std::ffi::OsStr>
where
T: ::std::borrow::Borrow<#inner_type>
{
iterable
.into_iter()
.map(|val| {
clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name()
})
.map(::std::ffi::OsStr::new)
.collect()

}

static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
iter_to_vals(#expr)
});
&*DEFAULT_VALUES.as_slice()
}
})
} else {
quote_spanned!(ident.span()=> {
{
fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<::std::ffi::OsString>
where
T: ::std::borrow::Borrow<#inner_type>
{
iterable.into_iter().map(|val| val.borrow().into()).collect()

}

static DEFAULT_OS_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::ffi::OsString>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
iter_to_vals(#expr)
});

static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
DEFAULT_OS_STRINGS.iter().map(::std::ffi::OsString::as_os_str).collect()
});
&*DEFAULT_VALUES.as_slice()
}
})
};

self.methods.push(Method::new(
Ident::new("default_values_os", ident.span()),
val,
));
}

NextDisplayOrder(ident, expr) => {
self.next_display_order = Some(Method::new(ident, quote!(#expr)));
}
Expand Down
4 changes: 4 additions & 0 deletions clap_derive/src/parse.rs
Expand Up @@ -53,7 +53,9 @@ pub enum ClapAttr {
// ident = arbitrary_expr
NameExpr(Ident, Expr),
DefaultValueT(Ident, Option<Expr>),
DefaultValuesT(Ident, Expr),
DefaultValueOsT(Ident, Option<Expr>),
DefaultValuesOsT(Ident, Expr),
NextDisplayOrder(Ident, Expr),
NextHelpHeading(Ident, Expr),
HelpHeading(Ident, Expr),
Expand Down Expand Up @@ -122,7 +124,9 @@ impl Parse for ClapAttr {
Ok(expr) => match &*name_str {
"skip" => Ok(Skip(name, Some(expr))),
"default_value_t" => Ok(DefaultValueT(name, Some(expr))),
"default_values_t" => Ok(DefaultValuesT(name, expr)),
"default_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))),
"default_values_os_t" => Ok(DefaultValuesOsT(name, expr)),
"next_display_order" => Ok(NextDisplayOrder(name, expr)),
"next_help_heading" => Ok(NextHelpHeading(name, expr)),
"help_heading" => Ok(HelpHeading(name, expr)),
Expand Down
6 changes: 6 additions & 0 deletions src/_derive/mod.rs
Expand Up @@ -231,9 +231,15 @@
//! - `default_value_t [= <expr>]`: [`Arg::default_value`][crate::Arg::default_value] and [`Arg::required(false)`][crate::Arg::required]
//! - Requires `std::fmt::Display` or `#[clap(value_enum)]`
//! - Without `<expr>`, relies on `Default::default()`
//! - `default_values_t = <expr>`: [`Arg::default_values`][crate::Arg::default_values] and [`Arg::required(false)`][crate::Arg::required]
//! - Requires field arg to be of type `Vec<T>` and `T` to implement `std::fmt::Display` or `#[clap(value_enum)]`
//! - `<expr>` must implement `IntoIterator<T>`
//! - `default_value_os_t [= <expr>]`: [`Arg::default_value_os`][crate::Arg::default_value_os] and [`Arg::required(false)`][crate::Arg::required]
//! - Requires `std::convert::Into<OsString>` or `#[clap(value_enum)]`
//! - Without `<expr>`, relies on `Default::default()`
//! - `default_values_os_t = <expr>`: [`Arg::default_values_os`][crate::Arg::default_values_os] and [`Arg::required(false)`][crate::Arg::required]
//! - Requires field arg to be of type `Vec<T>` and `T` to implement `std::convert::Into<OsString>` or `#[clap(value_enum)]`
//! - `<expr>` must implement `IntoIterator<T>`
//!
//! ### Value Enum Attributes
//!
Expand Down
96 changes: 95 additions & 1 deletion tests/derive/default_value.rs
Expand Up @@ -46,11 +46,71 @@ fn auto_default_value_t() {
assert!(help.contains("[default: 0]"));
}

#[test]
fn default_values_t() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(default_values_t = vec![1, 2, 3])]
arg1: Vec<i32>,

#[clap(long, default_values_t = &[4, 5, 6])]
arg2: Vec<i32>,

#[clap(long, default_values_t = [7, 8, 9])]
arg3: Vec<i32>,

#[clap(long, default_values_t = 10..=12)]
arg4: Vec<i32>,

#[clap(long, default_values_t = vec!["hello".to_string(), "world".to_string()])]
arg5: Vec<String>,

#[clap(long, default_values_t = &vec!["foo".to_string(), "bar".to_string()])]
arg6: Vec<String>,
}
assert_eq!(
Opt {
arg1: vec![1, 2, 3],
arg2: vec![4, 5, 6],
arg3: vec![7, 8, 9],
arg4: vec![10, 11, 12],
arg5: vec!["hello".to_string(), "world".to_string()],
arg6: vec!["foo".to_string(), "bar".to_string()],
},
Opt::try_parse_from(&["test"]).unwrap()
);
assert_eq!(
Opt {
arg1: vec![1],
arg2: vec![4, 5, 6],
arg3: vec![7, 8, 9],
arg4: vec![10, 11, 12],
arg5: vec!["hello".to_string(), "world".to_string()],
arg6: vec!["foo".to_string(), "bar".to_string()],
},
Opt::try_parse_from(&["test", "1"]).unwrap()
);
assert_eq!(
Opt {
arg1: vec![1, 2, 3],
arg2: vec![4, 5, 6],
arg3: vec![7, 8, 9],
arg4: vec![42, 15],
arg5: vec!["baz".to_string()],
arg6: vec!["foo".to_string(), "bar".to_string()],
},
Opt::try_parse_from(&["test", "--arg4", "42", "--arg4", "15", "--arg5", "baz"]).unwrap()
);

let help = utils::get_long_help::<Opt>();
assert!(help.contains("[default: 1 2 3]"));
}

#[test]
fn default_value_os_t() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(value_parser, default_value_os_t = PathBuf::from("abc.def"))]
#[clap(default_value_os_t = PathBuf::from("abc.def"))]
arg: PathBuf,
}
assert_eq!(
Expand All @@ -70,6 +130,40 @@ fn default_value_os_t() {
assert!(help.contains("[default: abc.def]"));
}

#[test]
fn default_values_os_t() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(
default_values_os_t = vec![PathBuf::from("abc.def"), PathBuf::from("123.foo")]
)]
arg1: Vec<PathBuf>,

#[clap(
long,
default_values_os_t = &[PathBuf::from("bar.baz")]
)]
arg2: Vec<PathBuf>,
}
assert_eq!(
Opt {
arg1: vec![PathBuf::from("abc.def"), PathBuf::from("123.foo")],
arg2: vec![PathBuf::from("bar.baz")]
},
Opt::try_parse_from(&["test"]).unwrap()
);
assert_eq!(
Opt {
arg1: vec![PathBuf::from("ghi")],
arg2: vec![PathBuf::from("baz.bar"), PathBuf::from("foo.bar")]
},
Opt::try_parse_from(&["test", "ghi", "--arg2", "baz.bar", "--arg2", "foo.bar"]).unwrap()
);

let help = utils::get_long_help::<Opt>();
assert!(help.contains("[default: abc.def 123.foo]"));
}

#[test]
fn detect_os_variant() {
#![allow(unused_parens)] // needed for `as_ref` call
Expand Down

0 comments on commit 69b0904

Please sign in to comment.