Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default value #312

Merged
merged 3 commits into from
Dec 29, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
59 changes: 53 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,8 +967,9 @@ pub use structopt_derive::*;

use std::ffi::OsString;

/// Re-export of clap
/// Re-exports
pub use clap;
pub use lazy_static;
CreepySkeleton marked this conversation as resolved.
Show resolved Hide resolved

/// A struct that is converted from command line arguments.
pub trait StructOpt {
Expand Down
59 changes: 56 additions & 3 deletions structopt-derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ use std::env;
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 quote::{format_ident, quote, quote_spanned, ToTokens};
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,39 @@ 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")
};

let static_name = format_ident!("__STRUCTOPT_DEFAULT_VALUE_{}", fresh_id());
CreepySkeleton marked this conversation as resolved.
Show resolved Hide resolved

quote_spanned!(ident.span()=> {
::structopt::lazy_static::lazy_static! {
static ref #static_name: &'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())
CreepySkeleton marked this conversation as resolved.
Show resolved Hide resolved
};
}
*#static_name
})
};

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 +395,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 +424,7 @@ impl Attrs {
field.span(),
Name::Derived(name.clone()),
parent_attrs,
Some(field.ty.clone()),
struct_casing,
env_casing,
);
Expand Down Expand Up @@ -603,6 +642,20 @@ impl Attrs {
}
}

fn fresh_id() -> usize {
use std::cell::Cell;

thread_local! {
static NEXT_ID: Cell<usize> = Cell::new(0);
}

NEXT_ID.with(|next_id| {
let id = next_id.get();
next_id.set(id + 1);
id
})
CreepySkeleton marked this conversation as resolved.
Show resolved Hide resolved
}

/// replace all `:` with `, ` when not inside the `<>`
///
/// `"author1:author2:author3" => "author1, author2, author3"`
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>),
TeXitoi marked this conversation as resolved.
Show resolved Hide resolved

// 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"]));
TeXitoi marked this conversation as resolved.
Show resolved Hide resolved

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