Repository: TeXitoi/structopt (Rust)

Issue:
version = expr ignored when using subcommands
This sounds similar to https://github.com/TeXitoi/structopt/issues/283 but manifests a little differently.

```rust
#[derive(StructOpt)]
#[structopt(version = my_version())]
struct Opt {
    #[structopt(subcommand)]
    cmd: Option<SubCommand>,
}

#[derive(StructOpt)]
struct StartCommand {
}

#[derive(StructOpt)]
enum SubCommand {
   Start(StartCommand),
}
```

generates code along the lines of:

```rust
impl StructOptInternal for Opt {
  fn augment_clap(app) {
      let app = app.version("1.0").version(my_version());
      let app = <SubCommand>::augment_clap(app);
      app
   }
}

impl StructOptInternal for Start {
   fn augment_clap(app) {
      // this overrides the version set on the top level, which is the bug
      let app = app.version("1.0");
      // ...
      app
}
```

The workaround is to add something like:

```
#[structopt(no_version, version = my_version())]
```

on everthing for which the `StructOpt` derive is used.


Discussion:
I really regret the decision to generate `version()` automatically when not asked, unlike `author` and `about`. It's third bug because of that.


I was going to suggest only setting it when explicitly referenced, but I haven't been following the changes since 0.2 closely enough to understand the background.
You mean, generate `.version()` call only when explicitly requested by `#[structopt(version)]`? We were considering it in #217 but we ended up with `no_version` instead, and I wholeheartedly regret this decision. I'll do my best to change in in `clap_derive` but, for `structopt`, it would be a breaking change.
That's a breaking change.

For the memory: https://github.com/TeXitoi/structopt/issues/217
My expectation would be that if I didn't explicitly use `#[structopt(version)]` then I would get a default version for the top level struct only, with the others leaving it unspecified.

I believe that that is the behavior in structopt 0.2 which is what I was attempting to upgrade from; from my perspective 0.2 -> 0.3 is already a breaking change, so I have no problem with a fix being an additional breaking change--it would make it less of a breaking change for me :)

FWIW, I'm also totally fine with not setting the default version at all, but that's because I prefer to show versions based off the commit information for executables.  So long as overriding the version is not overly laborious, I don't have a strong opinion on what the default behavior is.
> My expectation would be that if I didn't explicitly use #[structopt(version)] then I would get a default version for the top level struct only, with the others leaving it unspecified.

The question here, how to detect if the `struct` is top level or not? `StructOpt` uses only one trait for all the stuff: top level structs (apps), `flatten`ed structs, and `subcommand`s. 

It would be much easier to develop and support if they were *separate* traits, like `StructOpt`, `StructFlatten`, `StructSubcmd` or whatever, and this would also allow us to confidently say "yes, this is top level struct since `StructOpt` is used only on top level structs". But, unfortunately, this is a breaking change too. 

(Not to mention that this would simplify the implementing code significantly).

I'm looking forward to change it in `clap_derive`, we'll see then.
That would be more complicated for the user.
> I believe that that is the behavior in structopt 0.2 which is what I was attempting to upgrade from

Actually, it was [a longstanding bug](https://github.com/TeXitoi/structopt/issues/151) in 0.2, which [was fixed](https://github.com/TeXitoi/structopt/pull/290) only recently.

The bug in a nutshell - top level attributes (including the auto-generated `.version()` call) on inner structs (flattened, subcommands) had been ignored up until then. 
> 
> 
> That would be more complicated for the user.

What's complicated with separate traits? It would be like: 

> if you want to use the struct with `flatten`, it must derive `StructFlatten`.

> if you want to use a struct wit `subcommand`, it must derive `StructSubcommand`

> The top level app must derive `StructOpt`.

Pretty simple, huh?
You'll be surprised by the bug reports that would generate such a change.
Er, I'm not proposing to change `structopt`, heavens no. I'm just thinking aloud about "what `clap_derive` should look like".
Of course, just don't underestimate the simplicity of your interface, and don't forget that you are biased as a contributor.



Patch:
diff --git a/src/lib.rs b/src/lib.rs
index 716e0e8c..33a49882 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -213,12 +213,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
diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs
index 3cba66d5..411bb6a4 100644
--- a/structopt-derive/src/attrs.rs
+++ b/structopt-derive/src/attrs.rs
@@ -69,7 +69,7 @@ pub enum CasingStyle {
 #[derive(Clone)]
 pub enum Name {
     Derived(Ident),
-    Assigned(LitStr),
+    Assigned(TokenStream),
 }
 
 #[derive(Clone)]
@@ -193,11 +193,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 {
@@ -208,7 +208,7 @@ impl Name {
                     Snake => s.to_snake_case(),
                     Verbatim => s,
                 };
-                LitStr::new(&s, ident.span())
+                quote_spanned!(ident.span()=> #s)
             }
         }
     }
@@ -247,13 +247,13 @@ impl Attrs {
         }
     }
 
-    /// push `.method("str literal")`
-    fn push_str_method(&mut self, name: Sp<String>, arg: Sp<String>) {
-        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)))
         }
     }
 
@@ -263,17 +263,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) => {
@@ -336,16 +330,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);
@@ -567,27 +563,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
@@ -597,7 +578,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 0975569a..89720998 100644
--- a/structopt-derive/src/lib.rs
+++ b/structopt-derive/src/lib.rs
@@ -225,11 +225,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
     }}
 }
 
@@ -382,7 +383,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),
@@ -483,23 +484,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<T> Sp<T> {
     }
 }
 
-impl<T: ToString> Sp<T> {
-    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<T> Deref for Sp<T> {
     type Target = T;
 


Test patch:
diff --git a/tests/issues.rs b/tests/issues.rs
index 4d250aea..4f4ec93d 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<SubCommand>,
+    }
+
+    #[derive(StructOpt)]
+    enum SubCommand {
+        Start,
+    }
+
+    let help = get_long_help::<Opt>();
+    assert!(help.contains("MY_VERSION"));
+}