mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 14:54:15 +00:00
feat(derive): Specify defaults by native expressions
Right now - `default_value="something"` is a raw method - `default_value` uses native types This commit splits the meanings - `default_value="something"` is a raw method - `default_value_t` uses `T::default()` - `default_value_t=expr` uses an expression that evaluates to `T` This is meant to mirror the `value_of` / `value_of_t` API. At the moment, this is limited to `T: Display` to work with clap's default system. Something we can look at in the future is a way to loosen that restriction. One quick win is to specialize when `arg_enum` is set. The main downside is complicating the processing of attributes because it then means we need some processed before others. Since this builds on `clap`s existing default system, this also means users do not get any performance gains out of using `default_value_t`, since we still need to parse it but we also need to convert it to a string. Fixes #1694
This commit is contained in:
parent
476dd190b7
commit
9a180c1511
6 changed files with 91 additions and 29 deletions
|
@ -339,35 +339,37 @@ impl Attrs {
|
|||
|
||||
VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident),
|
||||
|
||||
DefaultValue(ident, lit) => {
|
||||
let val = if let Some(lit) = lit {
|
||||
quote!(#lit)
|
||||
DefaultValueT(ident, expr) => {
|
||||
let val = if let Some(expr) = expr {
|
||||
quote!(#expr)
|
||||
} else {
|
||||
let ty = if let Some(ty) = self.ty.as_ref() {
|
||||
ty
|
||||
} else {
|
||||
abort!(
|
||||
ident,
|
||||
"#[clap(default_value)] (without an argument) can be used \
|
||||
"#[clap(default_value_t)] (without an argument) can be used \
|
||||
only on field level";
|
||||
|
||||
note = "see \
|
||||
https://docs.rs/structopt/0.3.5/structopt/#magical-methods")
|
||||
};
|
||||
|
||||
quote_spanned!(ident.span()=> {
|
||||
clap::lazy_static::lazy_static! {
|
||||
static ref DEFAULT_VALUE: &'static str = {
|
||||
let val = <#ty as ::std::default::Default>::default();
|
||||
let s = ::std::string::ToString::to_string(&val);
|
||||
::std::boxed::Box::leak(s.into_boxed_str())
|
||||
};
|
||||
}
|
||||
*DEFAULT_VALUE
|
||||
})
|
||||
quote!(<#ty as ::std::default::Default>::default())
|
||||
};
|
||||
|
||||
self.methods.push(Method::new(ident, val));
|
||||
let val = quote_spanned!(ident.span()=> {
|
||||
clap::lazy_static::lazy_static! {
|
||||
static ref DEFAULT_VALUE: &'static str = {
|
||||
let val = #val;
|
||||
let s = ::std::string::ToString::to_string(&val);
|
||||
::std::boxed::Box::leak(s.into_boxed_str())
|
||||
};
|
||||
}
|
||||
*DEFAULT_VALUE
|
||||
});
|
||||
|
||||
let raw_ident = Ident::new("default_value", ident.span());
|
||||
self.methods.push(Method::new(raw_ident, val));
|
||||
}
|
||||
|
||||
About(ident, about) => {
|
||||
|
|
|
@ -26,7 +26,6 @@ pub enum ClapAttr {
|
|||
About(Ident, Option<LitStr>),
|
||||
Author(Ident, Option<LitStr>),
|
||||
Version(Ident, Option<LitStr>),
|
||||
DefaultValue(Ident, Option<LitStr>),
|
||||
|
||||
// ident = "string literal"
|
||||
RenameAllEnv(Ident, LitStr),
|
||||
|
@ -41,6 +40,7 @@ pub enum ClapAttr {
|
|||
|
||||
// ident = arbitrary_expr
|
||||
NameExpr(Ident, Expr),
|
||||
DefaultValueT(Ident, Option<Expr>),
|
||||
|
||||
// ident(arbitrary_expr,*)
|
||||
MethodCall(Ident, Vec<Expr>),
|
||||
|
@ -75,7 +75,6 @@ impl Parse for ClapAttr {
|
|||
match &*name_str {
|
||||
"rename_all" => Ok(RenameAll(name, lit)),
|
||||
"rename_all_env" => Ok(RenameAllEnv(name, lit)),
|
||||
"default_value" => Ok(DefaultValue(name, Some(lit))),
|
||||
|
||||
"version" => {
|
||||
check_empty_lit("version");
|
||||
|
@ -105,13 +104,11 @@ impl Parse for ClapAttr {
|
|||
}
|
||||
} else {
|
||||
match input.parse::<Expr>() {
|
||||
Ok(expr) => {
|
||||
if name_str == "skip" {
|
||||
Ok(Skip(name, Some(expr)))
|
||||
} else {
|
||||
Ok(NameExpr(name, expr))
|
||||
}
|
||||
}
|
||||
Ok(expr) => match &*name_str {
|
||||
"skip" => Ok(Skip(name, Some(expr))),
|
||||
"default_value_t" => Ok(DefaultValueT(name, Some(expr))),
|
||||
_ => Ok(NameExpr(name, expr)),
|
||||
},
|
||||
|
||||
Err(_) => abort! {
|
||||
assign_token,
|
||||
|
@ -176,7 +173,13 @@ impl Parse for ClapAttr {
|
|||
"external_subcommand" => Ok(ExternalSubcommand(name)),
|
||||
"verbatim_doc_comment" => Ok(VerbatimDocComment(name)),
|
||||
|
||||
"default_value" => Ok(DefaultValue(name, None)),
|
||||
"default_value" => {
|
||||
abort!(name,
|
||||
"`#[clap(default_value)` attribute (without a value) has been replaced by `#[clap(default_value_t)]`.";
|
||||
help = "Change the attribute to `#[clap(default_value_t)]`";
|
||||
)
|
||||
}
|
||||
"default_value_t" => Ok(DefaultValueT(name, None)),
|
||||
"about" => (Ok(About(name, None))),
|
||||
"author" => (Ok(Author(name, None))),
|
||||
"version" => Ok(Version(name, None)),
|
||||
|
|
|
@ -60,7 +60,7 @@ fn default_value() {
|
|||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum, default_value)]
|
||||
#[clap(arg_enum, default_value_t)]
|
||||
arg: ArgChoice,
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,38 @@ mod utils;
|
|||
use utils::*;
|
||||
|
||||
#[test]
|
||||
fn auto_default_value() {
|
||||
fn default_value() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(default_value)]
|
||||
#[clap(default_value = "3")]
|
||||
arg: i32,
|
||||
}
|
||||
assert_eq!(Opt { arg: 3 }, Opt::parse_from(&["test"]));
|
||||
assert_eq!(Opt { arg: 1 }, Opt::parse_from(&["test", "1"]));
|
||||
|
||||
let help = get_long_help::<Opt>();
|
||||
assert!(help.contains("[default: 3]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_value_t() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(default_value_t = 3)]
|
||||
arg: i32,
|
||||
}
|
||||
assert_eq!(Opt { arg: 3 }, Opt::parse_from(&["test"]));
|
||||
assert_eq!(Opt { arg: 1 }, Opt::parse_from(&["test", "1"]));
|
||||
|
||||
let help = get_long_help::<Opt>();
|
||||
assert!(help.contains("[default: 3]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_default_value_t() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(default_value_t)]
|
||||
arg: i32,
|
||||
}
|
||||
assert_eq!(Opt { arg: 0 }, Opt::parse_from(&["test"]));
|
||||
|
|
21
clap_derive/tests/ui/default_value_wo_value_removed.rs
Normal file
21
clap_derive/tests/ui/default_value_wo_value_removed.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
// 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::Clap;
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(name = "basic")]
|
||||
struct Opt {
|
||||
#[clap(default_value)]
|
||||
value: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::parse();
|
||||
println!("{:?}", opt);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
error: `#[clap(default_value)` attribute (without a value) has been replaced by `#[clap(default_value_t)]`.
|
||||
|
||||
= help: Change the attribute to `#[clap(default_value_t)]`
|
||||
|
||||
--> $DIR/default_value_wo_value_removed.rs:14:12
|
||||
|
|
||||
14 | #[clap(default_value)]
|
||||
| ^^^^^^^^^^^^^
|
Loading…
Reference in a new issue