Repository: TeXitoi/structopt (Rust)

Issue:
Stop doing automatic author, about and version
Since the begining, structopt use the CARGO_PKG environment variables to populate version, about and authors if not provided. As sometime you don't want that to be setted, there is an ugly hack that, if you do `author = ""`, the magic is removed.

That's not so in par with the clap philosophy, as these setters are not called by default on raw clap.

Thus I propose to remove the ugly hack, don't use CARGO_PKG by default, but add without argument options to set these fiels using the CARGO_PKG, i.e.

```rust
#[derive(StructOpt)]
struct Opt {}
```
became
```rust
#[derive(StructOpt)]
#[structopt(about, author, version)]
struct Opt {}
```
and
```rust
#[derive(StructOpt)]
#[structopt(about = "", author = "", version = "")]
struct Opt {}
```
became
```rust
#[derive(StructOpt)]
struct Opt {}
```

To anyone reading this, what do you think?

Would fix #172 and maybe #173 

cc @sphynx @killercup @kbknapp @ssokolow @robinst @0ndorio


Discussion:
I think the goal should be to default to a reasonable approximation of platform convention, similar to how Qt will try to default to matching the look of the target platform's GUI.

By that logic:

1. The current behaviour of `version` is perfectly reasonable as a default
2. The current behaviour of `about` is a footgun, because it competes with rustdoc for what purpose the struct's docstring should serve. (I just noticed *another* one of my projects slipped into having `--help` say `Command-line argument schema` because it decided to draw from the docstring that I include for the benefit of `rustdoc`.)
3. The current behaviour of `author` is at odds with GNU conventions, it's unusual enough that end-users are going to assume it's an opt-in decision and may mistake it for a sign that the structopt user is an egotist.

In short, I'd keep `version`'s current behaviour but, otherwise, I'm for this.
I like the general idea to move into  saner defaults and I think the changes you proposed are a step into the right direction. So just ping me as soon as you decided what to do and I will help to implement it.

But I also think we will run into some odds based on the default behavior of clap and I think it's worth to push a bit more on their side to get rid of them instead of working on structopt internal fixes. This mostly includes the following topics:

 - **Meta Information**: Next to the version number and the author the default template output also contains the binary name on the first line. This might look okay as long as these information are combined but I think it will start to annoy as soon as we hide the version number and the author by default.
    
   Personally I'm expecting to see all three of them only if I use `--version` as none of them is actually helpful when I try to find usage information but If the binary name stays at it currents position it would be great if `clap` would at least introduce an empty line between the leading meta information and the about text.

- **About Behavior**: In contrast to @ssokolow I like to use my doc strings as an alternative to custom `about` and `long_about` arguments but I can understand that not everybody feels like this. 

   I still think the main issue here is that `clap` always outputs `App::long_about` regardless if the user used `-h` or `--help` as initial argument. This becomes even worse if combined with custom templates as these forces the developer to either use `{about}` or `{long_about}` -- which by the way doesn't seem to be documented as an available placeholder but is still validated properly -- but doesn't provide a proper auto toggle version which respects the difference of `-h` and `--help`.

   Which  automatically leads to the strange and hard to predict combination of arguments necessary to disable that feature we discussed in TeXitoi/structopt#173. Another potential solution I can see would be a structopt top level argument which disables the doc string parsing in general.

   
> In contrast to @ssokolow I like to use my doc strings as an alternative to custom about and long_about arguments but I can understand that not everybody feels like this.

Which supports my point that "it competes with rustdoc for what purpose the struct's docstring should serve." (I see it as a situation comparable to not implementing `IntoIterator` for `&str` and `String` because the computer can't know whether you want bytes, codepoints, codepoint indexes, lines, etc.)
I'd say:
- `version` behaves just as it should, I have yet to see a case when it's not welcome. I vote for keeping it as is.
- `about` (doc comments): actually, I always use doc comments on `StructOpt` structs exactly for being processed by structopt, but I can see other's people point. I abstain.
- `author`: honestly, I had some problems with that, I vote for changing it. 
@TeXitoi Do you need any help on this? I'm going to have a vacation for a week, I'm quite familiar with the codebase and I'd love to make use of the upcoming features so just tell me what are you going to do and we may work it out.
@CreepySkeleton That would be great, I have difficulties to find enough time to do that.

I propose:
 - no default author and about
 - an attribute `author` without args that takes the cargo env
 - an attribute `about` without args that takes the cargo env
 - an attribute `no_version` without args that remove the version
 - no special casing of `""` anywhere

Also, at the same time, can you populate the CHANGELOG.md with your changes, I forgot to ask for that in your PR.

Thanks for your contribution.
Personally I like the current behavior as I think that it provides the best defaults for the majority of use cases and looks more pleasant. I'm also fine with not being in par with clap philosophy here. 

Although, I understand why it's going to change, and I'll be also fine with that.

As an alternative might I suggest to preserve the current behavior but also enhance structopt to generate `Opt::from_args_custom` method for situations where current behavior isn't appropriate? 

E.g.

```rust
#[derive(StructOpt)]
#[structopt(version = "1.0")]  // I want only version to be displayed in --help
struct Opt {
    ...
}

fn get_options() -> Opt {
    Opt::from_args_custom()
}

```
> I think that it provides the best defaults for the majority of use cases and looks more pleasant

It does, but it's not just about "majority of use cases", there are also "default behavior doesn't break anything" considerations. My case as an example:

I travel quite a lot with my job and sometimes I have no access to my own laptop. (Un)Fortunately, I tend to stay close to my friends so I often ask them to borrow theirs. Most of them have their own cargo installations with their own credentials, and I must admit that literally each one of my projects that needs command-line args parsing uses structopt. I'm sure you can see where we're going here - it wasn't just once when I accidentally started a project using wrong author credentials, thanks god they were closed-sourced and I had the authority to rewrite the history. (I also tend to forget to check git credentials but that's another story).

Also see @ssokolow comment about leaking internal comments into the public usage message.

So here's the point: now 9/10 of structopt users are happy with the default behavior and 1/10 users get f..reaked up if they aren't careful enough. With this changes we would have: 9/10 users will have to use `#[structopt(author, about)]` which is very easy and I doubt people would complain; 1/10 users will have no author/about behavior by default which is in my opinion much better than wrong behavior. 



> As an alternative might I suggest to preserve the current behavior but also enhance structopt to generate `Opt::from_args_custom` method for situations where current behavior isn't appropriate?

Sorry, I don't get it. Could you please explain how `Opt::from_args_custom` should work, exactly?
>So here's the point: now 9/10 of structopt users are happy with the default behavior and 1/10 users get f..reaked up if they aren't careful enough. 

But aren't they f..reaked up anyway by having the wrong credentials in `Cargo.toml`?

>Sorry, I don't get it. Could you please explain how Opt::from_args_custom should work, exactly?

Currently we have `from_args` function in `StructOpt` trait
https://github.com/TeXitoi/structopt/blob/5dfa60660d28f5016c0e229d010dd235e79e2491/src/lib.rs#L582-L589

I propose to rewrite it as following

```rust
 /// Gets the struct from the command line arguments.  Print the 
 /// error message and quit the program in case of failure. 
 fn from_args() -> Self 
 where 
     Self: Sized, 
 { 
     Self::from_clap(&Self::clap()
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .get_matches()
     ) 
 } 
```

And provide another function alongside which looks as `from_args` before

```rust
 /// Gets the struct from the command line arguments.  Print the 
 /// error message and quit the program in case of failure. Default
 /// entries from Cargo.toml will not be populated
 fn from_args_custom() -> Self 
 where 
     Self: Sized, 
 { 
     Self::from_clap(&Self::clap().get_matches()) 
 } 
```

It will be fully backward compatible way. However, it might have drawback that `from_iter` and `from_iter_safe` functions from `StructOpt` trait as well should have `_custom` duplicate, and users might be confused `version`, `about`, `author` on `Opt` doesn't have any effect. So, this might be wrong solution
I don't want a `from_arg_custom`, that overly complicated.

That's an API design choice, there is no good or bad somution, only traid of. That would be only me, I'd do my proposition for clap consistency. But clap is not really thought with no version in mind, and that's why I propose to keep version.

Also, note that the about thing is only about the description from the toml. The doc comment thing is some other thing, not related to this issue.

And for the author thing, I prefer the no author by default, more consistent with others and clap's defaults.
@TeXitoi I found some time and I'm working on it now, should be done by tomorrow. Just to be sure I get it right, feel free to correct:

- `version/author/about = "whatever"` methods stay intact
- the `version/about/author = ""` special cases are to be removed and should trigger an error
- add `about/author` methods with no args - they take their value from corresponding environment variables
- also we introduce `no_version` no args method which prevents "the version by default is to be taken from Cargo.toml` behavior.
- `version`, unlike `author/about`, keeps its old behavior unless `no_version` mentioned
Exact, except:
> the version/about/author = "" special cases are to be removed and should trigger an error

Error is not needed, just `.about("")` called is OK. If you prefer to do an error (maybe to explicitly warn the user of the old behavior), I'm OK if it doesn't complexify the code.
> maybe to explicitly warn the user of the old behavior

Exactly my point, people tend to stick to patterns they got used to, in my experience such explicit errors save lots of hours and sanity. The only thing - issuing a warning is impossible on stable rust so it's going to have to be a hard error.


> if it doesn't complexify the code

It shouldn't - an extra `match` or `if` statement will suffice



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 716c2848..446a15af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,17 +3,28 @@
 ## Breaking changes
 
 * Bump minimum rust version to 1.34 by [@TeXitoi](https://github.com/TeXitoi)
+
 * Support optional vectors of arguments for distinguishing between `-o 1 2`, `-o` and no option provided at all
   by [@sphynx](https://github.com/sphynx)
   ([#180](https://github.com/TeXitoi/structopt/issues/188)). This is a breaking change
   as `Option<Vec<T>>` is handled differently. If you need to have a `Option<Vec<T>>`
   handled the old was, you can `type Something = Vec<T>;` and then use `Option<Something>`
   as your structopt field.
+
 * Change default case from 'Verbatim' into 'Kebab' by [@0ndorio](https://github.com/0ndorio)
   ([#202](https://github.com/TeXitoi/structopt/issues/202)). This is a breaking change.
   If you rely on the old behavior you need to add `#[structopt(rename_all = "verbatim")]`
   as an attribute to each data structure deriving the `StructOpt` trait.
 
+* Change `version`, `author` and `about` attributes behavior. `version/author/about = ""` is no longer a
+  valid syntax. `author` and `about` are no longer derived from `Cargo.toml` by default, i.e when no
+  attributes mentioned. `version` is still to be derived from `Cargo.toml` by default.
+  Introduced explicit `author` and `about` attributes (with no arguments, i.e `#[structopt(author)]`)
+  for explicit requests to deduce author/about fields from `Cargo.toml`.
+  `version/author/about = "version/author/about"` is still valid syntax.
+  Introduced `no_version` attribute (no args) which prevents version auto deducing by default.
+  Proposed by [@TeXitoi](https://github.com/TeXitoi) [(#217)](https://github.com/TeXitoi/structopt/issues/217), implemented by [@CreepySkeleton](https://github.com/CreepySkeleton) [(#229)](https://github.com/TeXitoi/structopt/pull/229).
+
 ## improvements
 
 * Support skipping struct fields by [@sphynx](https://github.com/sphynx)
@@ -22,6 +33,9 @@
 * Add optional feature to support `paw` by [@gameldar](https://github.com/gameldar)
   ([#187](https://github.com/TeXitoi/structopt/issues/187))
 
+* Significantly improve error reporting by [@CreepySkeleton](https://github.com/CreepySkeleton)
+  ([#225](https://github.com/TeXitoi/structopt/pull/225/))
+
 # v0.2.16 (2019-05-29)
 
 * Support optional options with optional argument, allowing `cmd [--opt[=value]]`
diff --git a/examples/no_version.rs b/examples/no_version.rs
index 8d388cb1..8cf2550b 100644
--- a/examples/no_version.rs
+++ b/examples/no_version.rs
@@ -4,9 +4,7 @@ use structopt::StructOpt;
 #[derive(StructOpt, Debug)]
 #[structopt(
     name = "no_version",
-    about = "",
-    version = "",
-    author = "",
+    no_version,
     global_settings = &[AppSettings::DisableVersion]
 )]
 struct Opt {}
diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs
index 4f82eaf3..8358b0e0 100755
--- a/structopt-derive/src/attrs.rs
+++ b/structopt-derive/src/attrs.rs
@@ -5,18 +5,18 @@
 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
+
 use crate::spanned::Sp;
+
+use std::env;
+
 use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase};
 use proc_macro2::{Span, TokenStream};
-use proc_macro_error::span_error;
+use proc_macro_error::{call_site_error, span_error};
 use quote::quote;
-use std::{env, mem};
-use syn::spanned::Spanned as _;
-
-use syn::Type::Path;
 use syn::{
-    self, AngleBracketedGenericArguments, Attribute, GenericArgument, Ident, LitStr, MetaNameValue,
-    PathArguments, PathSegment, TypePath,
+    self, spanned::Spanned, AngleBracketedGenericArguments, Attribute, GenericArgument, Ident,
+    LitStr, MetaNameValue, PathArguments, PathSegment, Type::Path, TypePath,
 };
 
 use crate::parse::*;
@@ -39,12 +39,13 @@ pub enum Ty {
     Other,
 }
 
+#[derive(Clone)]
 pub struct Method {
     name: Ident,
     args: TokenStream,
 }
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
 pub enum Parser {
     FromStr,
     TryFromStr,
@@ -70,12 +71,17 @@ pub enum CasingStyle {
     Verbatim,
 }
 
+#[derive(Clone)]
 pub struct Attrs {
     name: Sp<String>,
     cased_name: String,
     casing: Sp<CasingStyle>,
     methods: Vec<Method>,
     parser: Sp<(Sp<Parser>, TokenStream)>,
+    author: Option<(Ident, LitStr)>,
+    about: Option<(Ident, LitStr)>,
+    version: Option<(Ident, LitStr)>,
+    no_version: Option<Ident>,
     has_custom_parser: bool,
     kind: Sp<Kind>,
 }
@@ -141,17 +147,19 @@ impl Attrs {
                 Sp::call_site(Parser::TryFromStr),
                 quote!(::std::str::FromStr::from_str),
             )),
+            about: None,
+            author: None,
+            version: None,
+            no_version: None,
+
             has_custom_parser: false,
             kind: Sp::call_site(Kind::Arg(Sp::call_site(Ty::Other))),
         }
     }
 
+    /// push `.method("str literal")`
     fn push_str_method(&mut self, name: Sp<String>, arg: Sp<String>) {
         match (&**name, &**arg) {
-            ("about", "") | ("version", "") | ("author", "") => {
-                let methods = mem::replace(&mut self.methods, vec![]);
-                self.methods = methods.into_iter().filter(|m| m.name != name).collect();
-            }
             ("name", _) => {
                 self.cased_name = self.casing.translate(&arg);
                 self.name = arg;
@@ -166,6 +174,21 @@ impl Attrs {
     fn push_attrs(&mut self, attrs: &[Attribute]) {
         use crate::parse::StructOptAttr::*;
 
+        fn from_lit_or_env(
+            ident: Ident,
+            lit: Option<LitStr>,
+            env_var: &str,
+        ) -> Option<(Ident, LitStr)> {
+            let lit = lit.unwrap_or_else(|| {
+                let gen = env::var(env_var)
+                    .unwrap_or_else(|_|
+                     span_error!(ident.span(), "`{}` environment variable is not defined, use `{} = \"{}\"` to set it manually", env_var, env_var, env_var));
+                LitStr::new(&gen, Span::call_site())
+            });
+
+            Some((ident, lit))
+        }
+
         for attr in parse_structopt_attributes(attrs) {
             match attr {
                 Short(ident) => {
@@ -194,6 +217,22 @@ impl Attrs {
                     self.set_kind(kind);
                 }
 
+                NoVersion(ident) => self.no_version = Some(ident),
+
+                About(ident, about) => {
+                    self.about = from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION")
+                }
+
+                Author(ident, author) => {
+                    self.author =
+                        from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS").map(|(ident, lit)| {
+                            let value = lit.value().replace(":", ", ");
+                            (ident.clone(), LitStr::new(&value, ident.span()))
+                        })
+                }
+
+                Version(ident, version) => self.version = Some((ident, version)),
+
                 NameLitStr(name, lit) => {
                     self.push_str_method(name.into(), lit.into());
                 }
@@ -205,7 +244,7 @@ impl Attrs {
 
                 MethodCall(name, args) => self.methods.push(Method {
                     name: name.into(),
-                    args: quote!(#args),
+                    args: quote!(#(#args),*),
                 }),
 
                 RenameAll(_, casing_lit) => {
@@ -272,6 +311,7 @@ impl Attrs {
                         return None;
                     }
                     let value = s.value();
+
                     let text = value
                         .trim_start_matches("//!")
                         .trim_start_matches("///")
@@ -335,33 +375,16 @@ impl Attrs {
             });
         }
     }
+
     pub fn from_struct(
         attrs: &[Attribute],
         name: Sp<String>,
         argument_casing: Sp<CasingStyle>,
     ) -> Self {
         let mut res = Self::new(name, argument_casing);
-        let attrs_with_env = [
-            ("version", "CARGO_PKG_VERSION"),
-            ("about", "CARGO_PKG_DESCRIPTION"),
-            ("author", "CARGO_PKG_AUTHORS"),
-        ];
-        attrs_with_env
-            .iter()
-            .filter_map(|&(m, v)| env::var(v).ok().and_then(|arg| Some((m, arg))))
-            .filter(|&(_, ref arg)| !arg.is_empty())
-            .for_each(|(name, arg)| {
-                let new_arg = if name == "author" {
-                    arg.replace(":", ", ")
-                } else {
-                    arg
-                };
-                let name = Sp::call_site(name.to_string());
-                let new_arg = Sp::call_site(new_arg.to_string());
-                res.push_str_method(name, new_arg);
-            });
-        res.push_doc_comment(attrs, "about");
         res.push_attrs(attrs);
+        res.push_doc_comment(attrs, "about");
+
         if res.has_custom_parser {
             span_error!(
                 res.parser.span(),
@@ -379,6 +402,7 @@ impl Attrs {
             Kind::Arg(_) => res,
         }
     }
+
     fn ty_from_field(ty: &syn::Type) -> Sp<Ty> {
         let t = |kind| Sp::new(kind, ty.span());
         if let Path(TypePath {
@@ -404,6 +428,7 @@ impl Attrs {
             t(Ty::Other)
         }
     }
+
     pub fn from_field(field: &syn::Field, struct_casing: Sp<CasingStyle>) -> Self {
         let name = field.ident.clone().unwrap();
         let mut res = Self::new(name.into(), struct_casing);
@@ -463,7 +488,7 @@ impl Attrs {
                     span_error!(m.name.span(), "methods are not allowed for skipped fields");
                 }
             }
-            Kind::Arg(_) => {
+            Kind::Arg(orig_ty) => {
                 let mut ty = Self::ty_from_field(&field.ty);
                 if res.has_custom_parser {
                     match *ty {
@@ -510,7 +535,7 @@ impl Attrs {
 
                     _ => (),
                 }
-                res.kind = Sp::call_site(Kind::Arg(ty));
+                res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());
             }
         }
 
@@ -536,16 +561,59 @@ impl Attrs {
         self.methods.iter().find(|m| m.name == name)
     }
 
-    pub fn methods(&self) -> TokenStream {
+    /// 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(_)) => span_error!(
+                no_version.span(),
+                "`no_version` and `version = \"version\"` can't be used together"
+            ),
+
+            (None, Some((_, version))) => quote!(.version(#version)),
+
+            (None, None) => {
+                let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_|{
+                    call_site_error!("`CARGO_PKG_VERSION` environment variable is not defined, use `version = \"version\" to set it manually or `no_version` to not set it at all")
+                });
+                quote!(.version(#version))
+            }
+
+            (Some(_), None) => TokenStream::new(),
+        };
+
+        let version = Some(version);
+        let author = self
+            .author
+            .as_ref()
+            .map(|(_, version)| quote!(.author(#version)));
+        let about = self
+            .about
+            .as_ref()
+            .map(|(_, version)| quote!(.about(#version)));
+
+        let methods = self
+            .methods
+            .iter()
+            .map(|&Method { ref name, ref args }| quote!( .#name(#args) ))
+            .chain(version)
+            .chain(author)
+            .chain(about);
+
+        quote!( #(#methods)* )
+    }
+
+    /// generate methods on top of a field
+    pub fn field_methods(&self) -> TokenStream {
         let methods = self
             .methods
             .iter()
             .map(|&Method { ref name, ref args }| quote!( .#name(#args) ));
+
         quote!( #(#methods)* )
     }
 
-    pub fn cased_name(&self) -> &str {
-        &self.cased_name
+    pub fn cased_name(&self) -> String {
+        self.cased_name.to_string()
     }
 
     pub fn parser(&self) -> &(Sp<Parser>, TokenStream) {
diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs
index 21716c83..2035a4cb 100644
--- a/structopt-derive/src/lib.rs
+++ b/structopt-derive/src/lib.rs
@@ -16,15 +16,15 @@ mod attrs;
 mod parse;
 mod spanned;
 
-use crate::attrs::{sub_type, Attrs, CasingStyle, Kind, Parser, Ty};
-use crate::spanned::Sp;
+use crate::{
+    attrs::{sub_type, Attrs, CasingStyle, Kind, Parser, Ty},
+    spanned::Sp,
+};
+
 use proc_macro2::{Span, TokenStream};
 use proc_macro_error::{call_site_error, filter_macro_errors, span_error};
 use quote::{quote, quote_spanned};
-use syn::punctuated::Punctuated;
-use syn::spanned::Spanned;
-use syn::token::Comma;
-use syn::*;
+use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, *};
 
 /// Default casing style for generated arguments.
 const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
@@ -154,8 +154,9 @@ fn gen_augmentation(
                         quote!( .takes_value(true).multiple(false).required(#required) #validator )
                     }
                 };
-                let methods = attrs.methods();
+
                 let name = attrs.cased_name();
+                let methods = attrs.field_methods();
 
                 Some(quote! {
                     let #app_var = #app_var.arg(
@@ -282,7 +283,7 @@ fn gen_clap(attrs: &[Attribute]) -> GenOutput {
     let attrs = Attrs::from_struct(attrs, Sp::call_site(name), Sp::call_site(DEFAULT_CASING));
     let tokens = {
         let name = attrs.cased_name();
-        let methods = attrs.methods();
+        let methods = attrs.top_level_methods();
 
         quote!(::structopt::clap::App::new(#name)#methods)
     };
@@ -372,7 +373,8 @@ fn gen_augment_clap_enum(
         };
 
         let name = attrs.cased_name();
-        let from_attrs = attrs.methods();
+        let from_attrs = attrs.top_level_methods();
+
         quote! {
             .subcommand({
                 let #app_var = ::structopt::clap::SubCommand::with_name(#name);
diff --git a/structopt-derive/src/parse.rs b/structopt-derive/src/parse.rs
index df7c7eaf..6714811c 100755
--- a/structopt-derive/src/parse.rs
+++ b/structopt-derive/src/parse.rs
@@ -1,8 +1,14 @@
+use std::iter::FromIterator;
+
 use proc_macro_error::{span_error, ResultExt};
-use syn::parse::{Parse, ParseStream};
-use syn::punctuated::Punctuated;
-use syn::spanned::Spanned;
-use syn::{self, parenthesized, parse2, Attribute, Expr, Ident, LitStr, Token};
+use syn::{
+    self, parenthesized,
+    parse::{Parse, ParseStream},
+    parse2,
+    punctuated::Punctuated,
+    spanned::Spanned,
+    Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token,
+};
 
 pub struct StructOptAttributes {
     pub paren_token: syn::token::Paren,
@@ -21,16 +27,31 @@ impl Parse for StructOptAttributes {
 }
 
 pub enum StructOptAttr {
+    // single-identifier attributes
     Short(Ident),
     Long(Ident),
     Flatten(Ident),
     Subcommand(Ident),
     Skip(Ident),
-    Parse(Ident, ParserSpec),
+    NoVersion(Ident),
+
+    // ident [= "string literal"]
+    About(Ident, Option<LitStr>),
+    Author(Ident, Option<LitStr>),
+
+    // ident = "string literal"
+    Version(Ident, LitStr),
     RenameAll(Ident, LitStr),
     NameLitStr(Ident, LitStr),
+
+    // parse(parser_kind [= parser_func])
+    Parse(Ident, ParserSpec),
+
+    // ident = arbitrary_expr
     NameExpr(Ident, Expr),
-    MethodCall(Ident, Punctuated<Expr, Token![,]>),
+
+    // ident(arbitrary_expr,*)
+    MethodCall(Ident, Vec<Expr>),
 }
 
 impl Parse for StructOptAttr {
@@ -42,22 +63,45 @@ impl Parse for StructOptAttr {
 
         if input.peek(Token![=]) {
             // `name = value` attributes.
-            input.parse::<Token![=]>()?; // skip '='
+            let assign_token = input.parse::<Token![=]>()?; // skip '='
 
-            match name_str.as_ref() {
-                "rename_all" => {
-                    let casing_lit: LitStr = input.parse()?;
-                    Ok(RenameAll(name, casing_lit))
-                }
+            if input.peek(LitStr) {
+                let lit: LitStr = input.parse()?;
+                let lit_str = lit.value();
 
-                _ => {
-                    if input.peek(LitStr) {
-                        let lit: LitStr = input.parse()?;
-                        Ok(NameLitStr(name, lit))
-                    } else {
-                        let expr: Expr = input.parse()?;
-                        Ok(NameExpr(name, expr))
+                let check_empty_lit = |s| {
+                    if lit_str.is_empty() {
+                        span_error!(lit.span(), "`#[structopt({} = \"\") is deprecated in structopt 3.0, now it's default behavior", s);
+                    }
+                };
+
+                match &*name_str.to_string() {
+                    "rename_all" => Ok(RenameAll(name, lit)),
+
+                    "version" => {
+                        check_empty_lit("version");
+                        Ok(Version(name, lit))
+                    }
+
+                    "author" => {
+                        check_empty_lit("author");
+                        Ok(Author(name, Some(lit)))
                     }
+
+                    "about" => {
+                        check_empty_lit("about");
+                        Ok(About(name, Some(lit)))
+                    }
+
+                    _ => Ok(NameLitStr(name, lit)),
+                }
+            } else {
+                match input.parse::<Expr>() {
+                    Ok(expr) => Ok(NameExpr(name, expr)),
+                    Err(_) => span_error! {
+                        assign_token.span(),
+                        "expected `string literal` or `expression` after `=`"
+                    },
                 }
             }
         } else if input.peek(syn::token::Paren) {
@@ -77,9 +121,22 @@ impl Parse for StructOptAttr {
                     }
                 }
 
+                "raw" => {
+                    match nested.parse::<LitBool>() {
+                        Ok(bool_token) => {
+                            let expr = ExprLit { attrs: vec![], lit: Lit::Bool(bool_token) };
+                            let expr = Expr::Lit(expr);
+                            Ok(MethodCall(name, vec![expr]))
+                        }
+
+                        Err(_) => span_error!(name.span(),
+                            "`#[structopt(raw(...))` attributes are deprecated in structopt 3.0, only `raw(true)` and `raw(false)` are allowed")
+                    }
+                }
+
                 _ => {
-                    let method_args = nested.parse_terminated(Expr::parse)?;
-                    Ok(MethodCall(name, method_args))
+                    let method_args: Punctuated<_, Token![,]> = nested.parse_terminated(Expr::parse)?;
+                    Ok(MethodCall(name, Vec::from_iter(method_args)))
                 }
             }
         } else {
@@ -90,6 +147,16 @@ impl Parse for StructOptAttr {
                 "flatten" => Ok(Flatten(name)),
                 "subcommand" => Ok(Subcommand(name)),
                 "skip" => Ok(Skip(name)),
+                "no_version" => Ok(NoVersion(name)),
+
+                "about" => (Ok(About(name, None))),
+                "author" => (Ok(Author(name, None))),
+
+                "version" => {
+                    span_error!(name.span(),
+                    "#[structopt(version)] is invalid attribute, structopt 3.0 inherits version from Cargo.toml by default, no attribute needed")
+                },
+
                 _ => span_error!(name.span(), "unexpected attribute: {}", name_str),
             }
         }
diff --git a/structopt-derive/src/spanned.rs b/structopt-derive/src/spanned.rs
index 5b781f18..dce37ded 100644
--- a/structopt-derive/src/spanned.rs
+++ b/structopt-derive/src/spanned.rs
@@ -65,6 +65,12 @@ impl From<LitStr> for Sp<String> {
     }
 }
 
+impl<'a> From<Sp<&'a str>> for Sp<String> {
+    fn from(sp: Sp<&'a str>) -> Self {
+        Sp::new(sp.val.into(), sp.span)
+    }
+}
+
 impl<T: PartialEq> PartialEq for Sp<T> {
     fn eq(&self, other: &Sp<T>) -> bool {
         self.val == other.val


Test patch:
diff --git a/tests/author_version_about.rs b/tests/author_version_about.rs
index 0872ea39..f28147c2 100644
--- a/tests/author_version_about.rs
+++ b/tests/author_version_about.rs
@@ -11,7 +11,7 @@ use structopt::StructOpt;
 #[test]
 fn no_author_version_about() {
     #[derive(StructOpt, PartialEq, Debug)]
-    #[structopt(name = "foo", about = "", author = "", version = "")]
+    #[structopt(name = "foo", no_version)]
     struct Opt {}
 
     let mut output = Vec::new();
@@ -24,7 +24,7 @@ fn no_author_version_about() {
 #[test]
 fn use_env() {
     #[derive(StructOpt, PartialEq, Debug)]
-    #[structopt()]
+    #[structopt(author, about)]
     struct Opt {}
 
     let mut output = Vec::new();
diff --git a/tests/doc-comments-help.rs b/tests/doc-comments-help.rs
index 4c7f2d61..1c9e6b62 100644
--- a/tests/doc-comments-help.rs
+++ b/tests/doc-comments-help.rs
@@ -57,7 +57,7 @@ fn empty_line_in_doc_comment_is_double_linefeed() {
     ///
     /// Bar
     #[derive(StructOpt, PartialEq, Debug)]
-    #[structopt(name = "lorem-ipsum", author = "", version = "")]
+    #[structopt(name = "lorem-ipsum", no_version)]
     struct LoremIpsum {}
 
     let mut output = Vec::new();
diff --git a/tests/raw_bool_literal.rs b/tests/raw_bool_literal.rs
new file mode 100644
index 00000000..4084b854
--- /dev/null
+++ b/tests/raw_bool_literal.rs
@@ -0,0 +1,30 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use structopt::clap;
+use structopt::StructOpt;
+
+#[test]
+fn raw_bool_literal() {
+    #[derive(StructOpt, Debug, PartialEq)]
+    #[structopt(no_version, name = "raw_bool")]
+    struct Opt {
+        #[structopt(raw(false))]
+        a: String,
+        #[structopt(raw(true))]
+        b: String,
+    }
+
+    assert_eq!(
+        Opt {
+            a: "one".into(),
+            b: "--help".into()
+        },
+        Opt::from_iter(&["test", "one", "--", "--help"])
+    );
+}
diff --git a/tests/ui/raw.rs b/tests/ui/raw.rs
new file mode 100644
index 00000000..cf75a444
--- /dev/null
+++ b/tests/ui/raw.rs
@@ -0,0 +1,17 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use structopt::StructOpt;
+
+#[derive(StructOpt, Debug)]
+struct Opt {
+    #[structopt(raw(case_insensitive = "true"))]
+    s: String,
+}
+
+fn main() {}
diff --git a/tests/ui/raw.stderr b/tests/ui/raw.stderr
new file mode 100644
index 00000000..e380dcd0
--- /dev/null
+++ b/tests/ui/raw.stderr
@@ -0,0 +1,5 @@
+error: `#[structopt(raw(...))` attributes are deprecated in structopt 3.0, only `raw(true)` and `raw(false)` are allowed
+  --> $DIR/raw.rs:13:17
+   |
+13 |     #[structopt(raw(case_insensitive = "true"))]
+   |                 ^^^