Skip to content

Commit

Permalink
Default value for default_value (yeah, sounds awkward)
Browse files Browse the repository at this point in the history
  • Loading branch information
CreepySkeleton authored Dec 29, 2019
1 parent ac1dea3 commit eaeb653
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* You don't have to apply `#[no_version]` to every `enum` variant anymore.
Just annotate the `enum` and the setting will be propagated down
([#242](https://github.com/TeXitoi/structopt/issues/242)).
* [Auto-default](https://docs.rs/structopt/0.3/structopt/#default-values).

# v0.3.7 (2019-12-28)

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ travis-ci = { repository = "TeXitoi/structopt" }
[dependencies]
clap = { version = "2.33", default-features = false }
structopt-derive = { path = "structopt-derive", version = "=0.4.0" }
lazy_static = "1.4.0"

[dev-dependencies]
trybuild = "1.0.5"
Expand Down
62 changes: 56 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
//! - Arguments
//! - [Type magic](#type-magic)
//! - [Specifying argument types](#specifying-argument-types)
//! - [Default values](#default-values)
//! - [Help messages](#help-messages)
//! - [Environment variable fallback](#environment-variable-fallback)
//! - [Skipping fields](#skipping-fields)
Expand Down Expand Up @@ -259,7 +260,12 @@
//!
//! Usable only on field-level.
//!
//! - [`rename_all`](#specifying-argument-types): [`rename_all = "kebab"/"snake"/"screaming-snake"/"camel"/"pascal"/"verbatim"]`
//! - [`defautl_value`](#default-values): `default_value [= "default value"]`
//!
//! Usable only on field-level.
//!
//! - [`rename_all`](#specifying-argument-types):
//! [`rename_all = "kebab"/"snake"/"screaming-snake"/"camel"/"pascal"/"verbatim"]`
//!
//! Usable both on top level and field level.
//!
Expand All @@ -283,7 +289,8 @@
//!
//! Usable only on field-level.
//!
//! - [`rename_all_env`](##auto-deriving-environment-variables): [`rename_all_env = "kebab"/"snake"/"screaming-snake"/"camel"/"pascal"/"verbatim"]`
//! - [`rename_all_env`](##auto-deriving-environment-variables):
//! [`rename_all_env = "kebab"/"snake"/"screaming-snake"/"camel"/"pascal"/"verbatim"]`
//!
//! Usable both on top level and field level.
//!
Expand Down Expand Up @@ -315,7 +322,7 @@
//! If you would like to use a custom string parser other than `FromStr`, see
//! the [same titled section](#custom-string-parsers) below.
//!
//! **Note:**
//! **Important:**
//! _________________
//! Pay attention that *only literal occurrence* of this types is special, for example
//! `Option<T>` is special while `::std::option::Option<T>` is not.
Expand Down Expand Up @@ -419,6 +426,47 @@
//! # }
//! ```
//!
//! ## Default values
//!
//! In clap, default values for options can be specified via [`Arg::default_value`].
//!
//! Of course, you can use as a raw method:
//! ```
//! # use structopt::StructOpt;
//! #[derive(StructOpt)]
//! struct Opt {
//! #[structopt(default_value = "", long)]
//! prefix: String
//! }
//! ```
//!
//! This is quite mundane and error-prone to type the `"..."` default by yourself,
//! especially when the Rust ecosystem uses the [`Default`] trait for that.
//! It would be wonderful to have `structopt` to take the `Default_default` and fill it
//! for you. And yes, `structopt` can do that.
//!
//! Unfortunately, `default_value` takes `&str` but `Default::default`
//! gives us some `Self` value. We need to map `Self` to `&str` somehow.
//!
//! `structopt` solves this problem via [`ToString`] trait.
//!
//! To be able to use auto-default the type must implement *both* `Default` and `ToString`:
//!
//! ```
//! # use structopt::StructOpt;
//! #[derive(StructOpt)]
//! struct Opt {
//! // just leave the `= "..."` part and structopt will figure it for you
//! #[structopt(default_value, long)]
//! prefix: String // `String` implements both `Default` and `ToString`
//! }
//! ```
//!
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
//! [`ToString`]: https://doc.rust-lang.org/std/string/trait.ToString.html
//! [`Arg::default_value`]: https://docs.rs/clap/2.33.0/clap/struct.Arg.html#method.default_value
//!
//!
//! ## Help messages
//!
//! In clap, help messages for the whole binary can be specified
Expand Down Expand Up @@ -583,8 +631,6 @@
//! ///
//! /// Hello!
//! ```
//!
//! Summary
//! ______________
//!
//! [`App::about`]: https://docs.rs/clap/2/clap/struct.App.html#method.about
Expand Down Expand Up @@ -921,9 +967,13 @@ pub use structopt_derive::*;

use std::ffi::OsString;

/// Re-export of clap
/// Re-exports
pub use clap;

/// **This is NOT PUBLIC API**.
#[doc(hidden)]
pub use lazy_static;

/// A struct that is converted from command line arguments.
pub trait StructOpt {
/// Returns the corresponding `clap::App`.
Expand Down
41 changes: 39 additions & 2 deletions structopt-derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase};
use proc_macro2::{Span, TokenStream};
use proc_macro_error::abort;
use quote::{quote, quote_spanned, ToTokens};
use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Ident, LitStr, MetaNameValue};
use syn::{
self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Ident, LitStr, MetaNameValue, Type,
};

#[derive(Clone)]
pub enum Kind {
Expand Down Expand Up @@ -75,6 +77,7 @@ pub struct Attrs {
name: Name,
casing: Sp<CasingStyle>,
env_casing: Sp<CasingStyle>,
ty: Option<Type>,
doc_comment: Vec<Method>,
methods: Vec<Method>,
parser: Sp<Parser>,
Expand Down Expand Up @@ -216,6 +219,7 @@ impl Attrs {
default_span: Span,
name: Name,
parent_attrs: Option<&Attrs>,
ty: Option<Type>,
casing: Sp<CasingStyle>,
env_casing: Sp<CasingStyle>,
) -> Self {
Expand All @@ -226,6 +230,7 @@ impl Attrs {

Self {
name,
ty,
casing,
env_casing,
doc_comment: vec![],
Expand Down Expand Up @@ -291,6 +296,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()=> {
::structopt::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");
}
Expand Down Expand Up @@ -357,7 +393,7 @@ impl Attrs {
argument_casing: Sp<CasingStyle>,
env_casing: Sp<CasingStyle>,
) -> Self {
let mut res = Self::new(span, name, parent_attrs, argument_casing, env_casing);
let mut res = Self::new(span, name, parent_attrs, None, argument_casing, env_casing);
res.push_attrs(attrs);
res.push_doc_comment(attrs, "about");

Expand Down Expand Up @@ -386,6 +422,7 @@ impl Attrs {
field.span(),
Name::Derived(name.clone()),
parent_attrs,
Some(field.ty.clone()),
struct_casing,
env_casing,
);
Expand Down
3 changes: 3 additions & 0 deletions structopt-derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub enum StructOptAttr {
// ident [= "string literal"]
About(Ident, Option<LitStr>),
Author(Ident, Option<LitStr>),
DefaultValue(Ident, Option<LitStr>),

// ident = "string literal"
Version(Ident, LitStr),
Expand Down Expand Up @@ -88,6 +89,7 @@ impl Parse for StructOptAttr {
match &*name_str.to_string() {
"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");
Expand Down Expand Up @@ -186,6 +188,7 @@ impl Parse for StructOptAttr {
"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))),

Expand Down
19 changes: 19 additions & 0 deletions tests/default_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use structopt::StructOpt;

mod utils;

use utils::*;

#[test]
fn auto_default_value() {
#[derive(StructOpt, PartialEq, Debug)]
struct Opt {
#[structopt(default_value)]
arg: i32,
}
assert_eq!(Opt { arg: 0 }, Opt::from_iter(&["test"]));
assert_eq!(Opt { arg: 1 }, Opt::from_iter(&["test", "1"]));

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

0 comments on commit eaeb653

Please sign in to comment.