diff --git a/Cargo.toml b/Cargo.toml index f26a98a47cb..db2e040e684 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,11 +53,12 @@ exitcode = "1.1.2" unicode-width = "0.1.4" textwrap = "0.11" indexmap = "1.0.1" -strsim = { version = "0.9.0", optional = true } -yaml-rust = { version = "0.4.1", optional = true } -atty = { version = "0.2.2", optional = true } -vec_map = { version = "0.8", optional = true } -term_size = { version = "1.0.0-beta1", optional = true } +strsim = { version = "0.9.0", optional = true } +yaml-rust = { version = "0.4.1", optional = true } +atty = { version = "0.2.2", optional = true } +vec_map = { version = "0.8", optional = true } +term_size = { version = "1.0.0-beta1", optional = true } +lazy_static = { version = "1", optional = true } clap_derive = { path = "./clap_derive", version = "3.0.0-beta.1", optional = true } [target.'cfg(not(windows))'.dependencies] @@ -74,7 +75,7 @@ std = [] # support for no_std in a backwards-compatible way suggestions = ["strsim"] color = ["ansi_term", "atty"] wrap_help = ["term_size", "textwrap/term_size"] -derive = ["clap_derive"] +derive = ["clap_derive", "lazy_static"] yaml = ["yaml-rust"] unstable = [] # for building with unstable clap features (doesn't require nightly Rust) (currently none) nightly = [] # for building with unstable Rust features (currently none) diff --git a/clap_derive/src/derives/attrs.rs b/clap_derive/src/derives/attrs.rs index e13f143f367..a70a6d6eb6b 100644 --- a/clap_derive/src/derives/attrs.rs +++ b/clap_derive/src/derives/attrs.rs @@ -20,7 +20,7 @@ use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase}; use proc_macro2::{self, Span, TokenStream}; use proc_macro_error::abort; use quote::{quote, quote_spanned, ToTokens}; -use syn::{self, ext::IdentExt, spanned::Spanned, Ident, LitStr, MetaNameValue}; +use syn::{self, ext::IdentExt, spanned::Spanned, Ident, LitStr, MetaNameValue, Type}; /// Default casing style for generated arguments. pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; @@ -87,6 +87,7 @@ pub struct Attrs { name: Name, casing: Sp, env_casing: Sp, + ty: Option, doc_comment: Vec, methods: Vec, parser: Sp, @@ -244,11 +245,13 @@ impl Attrs { fn new( default_span: Span, name: Name, + ty: Option, casing: Sp, env_casing: Sp, ) -> Self { Self { name, + ty, casing, env_casing, doc_comment: vec![], @@ -314,6 +317,37 @@ impl Attrs { VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident), + DefaultValue(ident, lit) => { + let val = if let Some(lit) = lit { + quote!(#lit) + } else { + let ty = if let Some(ty) = self.ty.as_ref() { + ty + } else { + abort!( + ident.span(), + "#[structopt(default_value)] (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 + }) + }; + + self.methods.push(Method::new(ident, val)); + } + About(ident, about) => { self.about = Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION"); } @@ -379,7 +413,7 @@ impl Attrs { argument_casing: Sp, env_casing: Sp, ) -> Self { - let mut res = Self::new(span, name, argument_casing, env_casing); + let mut res = Self::new(span, name, None, argument_casing, env_casing); res.push_attrs(attrs); res.push_doc_comment(attrs, "about"); @@ -406,6 +440,7 @@ impl Attrs { let mut res = Self::new( field.span(), Name::Derived(name.clone()), + Some(field.ty.clone()), struct_casing, env_casing, ); diff --git a/clap_derive/src/derives/parse.rs b/clap_derive/src/derives/parse.rs index aa0a0b2b988..87ee307a4b2 100644 --- a/clap_derive/src/derives/parse.rs +++ b/clap_derive/src/derives/parse.rs @@ -40,6 +40,7 @@ pub enum ClapAttr { // ident [= "string literal"] About(Ident, Option), Author(Ident, Option), + DefaultValue(Ident, Option), // ident = "string literal" Version(Ident, LitStr), @@ -89,6 +90,7 @@ 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"); @@ -187,6 +189,7 @@ impl Parse for ClapAttr { "no_version" => Ok(NoVersion(name)), "verbatim_doc_comment" => Ok(VerbatimDocComment(name)), + "default_value" => Ok(DefaultValue(name, None)), "about" => (Ok(About(name, None))), "author" => (Ok(Author(name, None))), diff --git a/clap_derive/tests/default_value.rs b/clap_derive/tests/default_value.rs new file mode 100644 index 00000000000..6666ac589bd --- /dev/null +++ b/clap_derive/tests/default_value.rs @@ -0,0 +1,19 @@ +use clap::Clap; + +mod utils; + +use utils::*; + +#[test] +fn auto_default_value() { + #[derive(Clap, PartialEq, Debug)] + struct Opt { + #[clap(default_value)] + arg: i32, + } + assert_eq!(Opt { arg: 0 }, Opt::parse_from(&["test"])); + assert_eq!(Opt { arg: 1 }, Opt::parse_from(&["test", "1"])); + + let help = get_long_help::(); + assert!(help.contains("[default: 0]")); +} diff --git a/clap_derive/tests/ui/raw.stderr b/clap_derive/tests/ui/raw.stderr index 5f71c8408a7..c21c008379e 100644 --- a/clap_derive/tests/ui/raw.stderr +++ b/clap_derive/tests/ui/raw.stderr @@ -1,7 +1,7 @@ error: `#[clap(raw(...))` attributes are removed, they are replaced with raw methods = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` - = note: if you need to call `clap::Arg/App::case_insensitive` method you can do it like this: #[structopt(case_insensitive = true)] + = note: if you need to call `clap::Arg/App::case_insensitive` method you can do it like this: #[clap(case_insensitive = true)] --> $DIR/raw.rs:13:12 | @@ -11,7 +11,7 @@ error: `#[clap(raw(...))` attributes are removed, they are replaced with raw met error: `#[clap(raw(...))` attributes are removed, they are replaced with raw methods = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` - = note: if you need to call `clap::Arg/App::requires_if` method you can do it like this: #[structopt(requires_if("one", "two"))] + = note: if you need to call `clap::Arg/App::requires_if` method you can do it like this: #[clap(requires_if("one", "two"))] --> $DIR/raw.rs:19:12 | diff --git a/src/lib.rs b/src/lib.rs index b23080e163f..300c292e177 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -457,7 +457,11 @@ pub use yaml_rust::YamlLoader; #[cfg(feature = "derive")] #[cfg_attr(feature = "derive", doc(hidden))] -pub use clap_derive::*; +pub use clap_derive::{self, *}; + +#[cfg(feature = "derive")] +#[cfg_attr(feature = "derive", doc(hidden))] +pub use lazy_static; use std::result::Result as StdResult;