feat(clap_derive): Add default_values_t and default_values_os_t

This commit is contained in:
Emerson Ford 2022-07-24 16:10:31 -07:00
parent 732a21b1bf
commit 04e0ed7474
7 changed files with 406 additions and 0 deletions

View file

@ -516,6 +516,81 @@ impl Attrs {
self.methods.push(Method::new(raw_ident, val)); 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) => { DefaultValueOsT(ident, expr) => {
let ty = if let Some(ty) = self.ty.as_ref() { let ty = if let Some(ty) = self.ty.as_ref() {
ty ty
@ -556,6 +631,84 @@ impl Attrs {
self.methods.push(Method::new(raw_ident, val)); 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) => { NextDisplayOrder(ident, expr) => {
self.next_display_order = Some(Method::new(ident, quote!(#expr))); self.next_display_order = Some(Method::new(ident, quote!(#expr)));
} }

View file

@ -51,7 +51,9 @@ pub enum ClapAttr {
// ident = arbitrary_expr // ident = arbitrary_expr
NameExpr(Ident, Expr), NameExpr(Ident, Expr),
DefaultValueT(Ident, Option<Expr>), DefaultValueT(Ident, Option<Expr>),
DefaultValuesT(Ident, Expr),
DefaultValueOsT(Ident, Option<Expr>), DefaultValueOsT(Ident, Option<Expr>),
DefaultValuesOsT(Ident, Expr),
NextDisplayOrder(Ident, Expr), NextDisplayOrder(Ident, Expr),
NextHelpHeading(Ident, Expr), NextHelpHeading(Ident, Expr),
HelpHeading(Ident, Expr), HelpHeading(Ident, Expr),
@ -120,7 +122,9 @@ impl Parse for ClapAttr {
Ok(expr) => match &*name_str { Ok(expr) => match &*name_str {
"skip" => Ok(Skip(name, Some(expr))), "skip" => Ok(Skip(name, Some(expr))),
"default_value_t" => Ok(DefaultValueT(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_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))),
"default_values_os_t" => Ok(DefaultValuesOsT(name, expr)),
"next_display_order" => Ok(NextDisplayOrder(name, expr)), "next_display_order" => Ok(NextDisplayOrder(name, expr)),
"next_help_heading" => Ok(NextHelpHeading(name, expr)), "next_help_heading" => Ok(NextHelpHeading(name, expr)),
"help_heading" => Ok(HelpHeading(name, expr)), "help_heading" => Ok(HelpHeading(name, expr)),

View file

@ -223,9 +223,15 @@
//! - `default_value_t [= <expr>]`: [`Arg::default_value`][crate::Arg::default_value] and [`Arg::required(false)`][crate::Arg::required] //! - `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)]` //! - Requires `std::fmt::Display` or `#[clap(value_enum)]`
//! - Without `<expr>`, relies on `Default::default()` //! - 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] //! - `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)]` //! - Requires `std::convert::Into<OsString>` or `#[clap(value_enum)]`
//! - Without `<expr>`, relies on `Default::default()` //! - 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 //! ### Value Enum Attributes
//! //!

View file

@ -1,3 +1,5 @@
use std::path::PathBuf;
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use crate::utils; use crate::utils;
@ -44,6 +46,124 @@ fn auto_default_value_t() {
assert!(help.contains("[default: 0]")); 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(default_value_os_t = PathBuf::from("abc.def"))]
arg: PathBuf,
}
assert_eq!(
Opt {
arg: PathBuf::from("abc.def")
},
Opt::try_parse_from(&["test"]).unwrap()
);
assert_eq!(
Opt {
arg: PathBuf::from("ghi")
},
Opt::try_parse_from(&["test", "ghi"]).unwrap()
);
let help = utils::get_long_help::<Opt>();
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] #[test]
fn detect_os_variant() { fn detect_os_variant() {
#![allow(unused_parens)] // needed for `as_ref` call #![allow(unused_parens)] // needed for `as_ref` call

View file

@ -78,6 +78,108 @@ fn default_value() {
); );
} }
#[test]
fn vec_for_default_values_t() {
#[derive(clap::ValueEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(value_enum, default_values_t = vec![ArgChoice::Foo, ArgChoice::Bar])]
arg1: Vec<ArgChoice>,
#[clap(
long,
value_enum,
default_values_t = clap::ValueEnum::value_variants()
)]
arg2: Vec<ArgChoice>,
}
assert_eq!(
Opt {
arg1: vec![ArgChoice::Foo],
arg2: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&["", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg1: vec![ArgChoice::Bar],
arg2: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&["", "bar"]).unwrap()
);
assert_eq!(
Opt {
arg1: vec![ArgChoice::Foo, ArgChoice::Bar],
arg2: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&[""]).unwrap()
);
assert_eq!(
Opt {
arg1: vec![ArgChoice::Foo, ArgChoice::Bar],
arg2: vec![ArgChoice::Foo]
},
Opt::try_parse_from(&["", "--arg2", "foo"]).unwrap()
);
}
#[test]
fn vec_for_default_values_os_t() {
#[derive(clap::ValueEnum, PartialEq, Debug, Clone)]
enum ArgChoice {
Foo,
Bar,
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(value_enum, default_values_os_t = vec![ArgChoice::Foo, ArgChoice::Bar])]
arg: Vec<ArgChoice>,
#[clap(
long,
value_enum,
default_values_os_t = clap::ValueEnum::value_variants()
)]
arg2: Vec<ArgChoice>,
}
assert_eq!(
Opt {
arg: vec![ArgChoice::Foo],
arg2: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&["", "foo"]).unwrap()
);
assert_eq!(
Opt {
arg: vec![ArgChoice::Bar],
arg2: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&["", "bar"]).unwrap()
);
assert_eq!(
Opt {
arg: vec![ArgChoice::Foo, ArgChoice::Bar],
arg2: vec![ArgChoice::Foo, ArgChoice::Bar]
},
Opt::try_parse_from(&[""]).unwrap()
);
assert_eq!(
Opt {
arg: vec![ArgChoice::Foo, ArgChoice::Bar],
arg2: vec![ArgChoice::Foo]
},
Opt::try_parse_from(&["", "--arg2", "foo"]).unwrap()
);
}
#[test] #[test]
fn multi_word_is_renamed_kebab() { fn multi_word_is_renamed_kebab() {
#[derive(clap::ValueEnum, PartialEq, Debug, Clone)] #[derive(clap::ValueEnum, PartialEq, Debug, Clone)]

View file

@ -0,0 +1,13 @@
use clap::Parser;
#[derive(Parser, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(default_values_t = [1, 2, 3])]
value: u32,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,8 @@
error: #[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
--> tests/derive_ui/default_values_t_invalid.rs:6:12
|
6 | #[clap(default_values_t = [1, 2, 3])]
| ^^^^^^^^^^^^^^^^