From 24eb86dc24e02554dc7f7b58f7d5310503581a28 Mon Sep 17 00:00:00 2001 From: CreepySkeleton Date: Sat, 11 Jan 2020 18:56:43 +0300 Subject: [PATCH] Bugfix #324 Fixes https://github.com/TeXitoi/structopt/issues/324 --- src/lib.rs | 15 +++++-- structopt-derive/src/attrs.rs | 71 +++++++++++++++------------------ structopt-derive/src/lib.rs | 13 +++--- structopt-derive/src/spanned.rs | 10 ----- tests/issues.rs | 30 +++++++++++++- 5 files changed, 79 insertions(+), 60 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 757d0848..e81e069d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,12 +214,12 @@ //! } //! ``` //! -//! - `name`: `[name = "name"]` -//! - On top level: `App::new("name")`. +//! - `name`: `[name = expr]` +//! - On top level: `App::new(expr)`. //! //! The binary name displayed in help messages. Defaults to the crate name given by Cargo. //! -//! - On field-level: `Arg::with_name("name")`. +//! - On field-level: `Arg::with_name(expr)`. //! //! The name for the argument the field stands for, this name appears in help messages. //! Defaults to a name, deduced from a field, see also @@ -1090,6 +1090,10 @@ pub trait StructOptInternal: StructOpt { { None } + + fn augment_version<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { + app + } } impl StructOpt for Box { @@ -1117,4 +1121,9 @@ impl StructOptInternal for Box { fn augment_clap<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { ::augment_clap(app) } + + #[doc(hidden)] + fn augment_version<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { + ::augment_version(app) + } } diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index 15d94979..c4e91e02 100644 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -70,7 +70,7 @@ pub enum CasingStyle { #[derive(Clone)] pub enum Name { Derived(Ident), - Assigned(LitStr), + Assigned(TokenStream), } #[derive(Clone)] @@ -194,11 +194,11 @@ impl CasingStyle { } impl Name { - pub fn translate(self, style: CasingStyle) -> LitStr { + pub fn translate(self, style: CasingStyle) -> TokenStream { use CasingStyle::*; match self { - Name::Assigned(lit) => lit, + Name::Assigned(tokens) => tokens, Name::Derived(ident) => { let s = ident.unraw().to_string(); let s = match style { @@ -209,7 +209,7 @@ impl Name { Snake => s.to_snake_case(), Verbatim => s, }; - LitStr::new(&s, ident.span()) + quote_spanned!(ident.span()=> #s) } } } @@ -248,13 +248,13 @@ impl Attrs { } } - /// push `.method("str literal")` - fn push_str_method(&mut self, name: Sp, arg: Sp) { - if *name == "name" { - self.name = Name::Assigned(arg.as_lit()); + fn push_method(&mut self, name: Ident, arg: impl ToTokens) { + if name == "name" { + self.name = Name::Assigned(quote!(#arg)); + } else if name == "version" { + self.version = Some(Method::new(name, quote!(#arg))); } else { - self.methods - .push(Method::new(name.as_ident(), quote!(#arg))) + self.methods.push(Method::new(name, quote!(#arg))) } } @@ -264,17 +264,11 @@ impl Attrs { for attr in parse_structopt_attributes(attrs) { match attr { Short(ident) | Long(ident) => { - self.push_str_method( - ident.into(), - self.name.clone().translate(*self.casing).into(), - ); + self.push_method(ident, self.name.clone().translate(*self.casing)); } Env(ident) => { - self.push_str_method( - ident.into(), - self.name.clone().translate(*self.env_casing).into(), - ); + self.push_method(ident, self.name.clone().translate(*self.env_casing)); } Subcommand(ident) => { @@ -341,16 +335,18 @@ impl Attrs { } Version(ident, version) => { - self.version = Some(Method::new(ident, quote!(#version))) + self.push_method(ident, version); } NameLitStr(name, lit) => { - self.push_str_method(name.into(), lit.into()); + self.push_method(name, lit); } - NameExpr(name, expr) => self.methods.push(Method::new(name, quote!(#expr))), + NameExpr(name, expr) => { + self.push_method(name, expr); + } - MethodCall(name, args) => self.methods.push(Method::new(name, quote!(#(#args),*))), + MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)), RenameAll(_, casing_lit) => { self.casing = CasingStyle::from_lit(casing_lit); @@ -575,27 +571,12 @@ impl Attrs { /// generate methods from attributes on top of struct or enum pub fn top_level_methods(&self) -> TokenStream { - let version = match (&self.no_version, &self.version) { - (Some(no_version), Some(_)) => abort!( - no_version.span(), - "`no_version` and `version = \"version\"` can't be used together" - ), - - (None, Some(m)) => m.to_token_stream(), - - (None, None) => std::env::var("CARGO_PKG_VERSION") - .map(|version| quote!( .version(#version) )) - .unwrap_or_default(), - - (Some(_), None) => quote!(), - }; - let author = &self.author; let about = &self.about; let methods = &self.methods; let doc_comment = &self.doc_comment; - quote!( #(#doc_comment)* #author #version #about #(#methods)* ) + quote!( #(#doc_comment)* #author #about #(#methods)* ) } /// generate methods on top of a field @@ -605,7 +586,19 @@ impl Attrs { quote!( #(#doc_comment)* #(#methods)* ) } - pub fn cased_name(&self) -> LitStr { + pub fn version(&self) -> TokenStream { + match (&self.no_version, &self.version) { + (None, Some(m)) => m.to_token_stream(), + + (None, None) => std::env::var("CARGO_PKG_VERSION") + .map(|version| quote!( .version(#version) )) + .unwrap_or_default(), + + _ => quote!(), + } + } + + pub fn cased_name(&self) -> TokenStream { self.name.clone().translate(*self.casing) } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 93d6a447..66e9311e 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -230,11 +230,12 @@ fn gen_augmentation( }); let app_methods = parent_attribute.top_level_methods(); + let version = parent_attribute.version(); quote! {{ let #app_var = #app_var#app_methods; #( #args )* #subcmd - #app_var + #app_var#version }} } @@ -392,7 +393,7 @@ fn gen_clap(attrs: &[Attribute]) -> GenOutput { let attrs = Attrs::from_struct( Span::call_site(), attrs, - Name::Assigned(LitStr::new(&name, Span::call_site())), + Name::Assigned(quote!(#name)), None, Sp::call_site(DEFAULT_CASING), Sp::call_site(DEFAULT_ENV_CASING), @@ -500,23 +501,23 @@ fn gen_augment_clap_enum( let name = attrs.cased_name(); let from_attrs = attrs.top_level_methods(); - + let version = attrs.version(); quote! { .subcommand({ let #app_var = ::structopt::clap::SubCommand::with_name(#name); let #app_var = #arg_block; - #app_var#from_attrs + #app_var#from_attrs#version }) } }); let app_methods = parent_attribute.top_level_methods(); - + let version = parent_attribute.version(); quote! { fn augment_clap<'a, 'b>( app: ::structopt::clap::App<'a, 'b> ) -> ::structopt::clap::App<'a, 'b> { - app #app_methods #( #subcommands )* + app #app_methods #( #subcommands )* #version } } } diff --git a/structopt-derive/src/spanned.rs b/structopt-derive/src/spanned.rs index 2dd595bb..8abe3052 100644 --- a/structopt-derive/src/spanned.rs +++ b/structopt-derive/src/spanned.rs @@ -27,16 +27,6 @@ impl Sp { } } -impl Sp { - pub fn as_ident(&self) -> Ident { - Ident::new(&self.to_string(), self.span) - } - - pub fn as_lit(&self) -> LitStr { - LitStr::new(&self.to_string(), self.span) - } -} - impl Deref for Sp { type Target = T; diff --git a/tests/issues.rs b/tests/issues.rs index 4d250aea..7cff88b0 100644 --- a/tests/issues.rs +++ b/tests/issues.rs @@ -1,5 +1,9 @@ -// https://github.com/TeXitoi/structopt/issues/151 -// https://github.com/TeXitoi/structopt/issues/289 +// https://github.com/TeXitoi/structopt/issues/{NUMBER} + +mod utils; +use utils::*; + +use structopt::StructOpt; #[test] fn issue_151() { @@ -65,3 +69,25 @@ fn issue_289() { .get_matches_from_safe(&["test", "some", "test"]) .is_ok()); } + +#[test] +fn issue_324() { + fn my_version() -> &'static str { + "MY_VERSION" + } + + #[derive(StructOpt)] + #[structopt(version = my_version())] + struct Opt { + #[structopt(subcommand)] + cmd: Option, + } + + #[derive(StructOpt)] + enum SubCommand { + Start, + } + + let help = get_long_help::(); + assert!(help.contains("MY_VERSION")); +}