diff --git a/CHANGELOG.md b/CHANGELOG.md index 63e361ff..87c97c11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v0.3.2 +* `structopt` does not replace `:` with `, ` inside "author" strings while inside `<...>`. + Fixes [#156](https://github.com/TeXitoi/structopt/issues/156) + # v0.3.1 (2019-09-06) * Fix error messages ([#241](https://github.com/TeXitoi/structopt/issues/241)) diff --git a/src/lib.rs b/src/lib.rs index 26ef3feb..e57e8c0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,7 +183,8 @@ //! - On field-level: `Arg::with_name("name")`. //! //! 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 [`rename_all`](#specifying-argument-types). +//! Defaults to a name, deduced from a field, see also +//! [`rename_all`](#specifying-argument-types). //! //! - `version`: `[version = "version"]` //! diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index 04724c2d..1ff0023e 100644 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -6,21 +6,19 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::spanned::Sp; +use crate::{parse::*, spanned::Sp}; use std::env; use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase}; use proc_macro2::{Span, TokenStream}; use proc_macro_error::span_error; -use quote::quote; +use quote::{quote, quote_spanned, ToTokens}; use syn::{ self, spanned::Spanned, AngleBracketedGenericArguments, Attribute, GenericArgument, Ident, LitStr, MetaNameValue, PathArguments, PathSegment, Type::Path, TypePath, }; -use crate::parse::*; - #[derive(Clone, Debug)] pub enum Kind { Arg(Sp), @@ -45,8 +43,14 @@ pub struct Method { args: TokenStream, } +#[derive(Clone)] +pub struct Parser { + pub kind: Sp, + pub func: TokenStream, +} + #[derive(Debug, PartialEq, Clone)] -pub enum Parser { +pub enum ParserKind { FromStr, TryFromStr, FromOsStr, @@ -77,28 +81,91 @@ pub struct Attrs { cased_name: String, casing: Sp, methods: Vec, - parser: Sp<(Sp, TokenStream)>, - author: Option<(Ident, LitStr)>, - about: Option<(Ident, LitStr)>, - version: Option<(Ident, LitStr)>, + parser: Sp, + author: Option, + about: Option, + version: Option, no_version: Option, has_custom_parser: bool, kind: Sp, } -impl Parser { - fn from_ident(ident: Ident) -> Sp { - use Parser::*; - - let p = |kind| Sp::new(kind, ident.span()); - match &*ident.to_string() { - "from_str" => p(FromStr), - "try_from_str" => p(TryFromStr), - "from_os_str" => p(FromOsStr), - "try_from_os_str" => p(TryFromOsStr), - "from_occurrences" => p(FromOccurrences), - s => span_error!(ident.span(), "unsupported parser `{}`", s), +impl Method { + fn new(name: Ident, args: TokenStream) -> Self { + Method { name, args } + } + + fn from_lit_or_env(ident: Ident, lit: Option, env_var: &str) -> Option { + let mut lit = match lit { + Some(lit) => lit, + + None => match env::var(env_var) { + Ok(val) => LitStr::new(&val, ident.span()), + Err(_) => { + span_error!(ident.span(), + "`{}` environment variable is not defined, use `{} = \"{}\"` to set it manually", env_var, ident, ident + ); + } + }, + }; + + if ident == "author" { + let edited = process_author_str(&lit.value()); + lit = LitStr::new(&edited, lit.span()); } + + Some(Method::new(ident, quote!(#lit))) + } +} + +impl ToTokens for Method { + fn to_tokens(&self, ts: &mut TokenStream) { + let Method { ref name, ref args } = self; + quote!(.#name(#args)).to_tokens(ts); + } +} + +impl Parser { + fn default_spanned(span: Span) -> Sp { + let kind = Sp::new(ParserKind::TryFromStr, span.clone()); + let func = quote_spanned!(span=> ::std::str::FromStr::from_str); + Sp::new(Parser { kind, func }, span) + } + + fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp { + use ParserKind::*; + + let kind = match &*spec.kind.to_string() { + "from_str" => FromStr, + "try_from_str" => TryFromStr, + "from_os_str" => FromOsStr, + "try_from_os_str" => TryFromOsStr, + "from_occurrences" => FromOccurrences, + s => span_error!(spec.kind.span(), "unsupported parser `{}`", s), + }; + + let func = match spec.parse_func { + None => match kind { + FromStr | FromOsStr => { + quote_spanned!(spec.kind.span()=> ::std::convert::From::from) + } + TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str), + TryFromOsStr => span_error!( + spec.kind.span(), + "cannot omit parser function name with `try_from_os_str`" + ), + FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }), + }, + + Some(func) => match func { + syn::Expr::Path(_) => quote!(#func), + _ => span_error!(func.span(), "`parse` argument must be a function path"), + }, + }; + + let kind = Sp::new(kind, spec.kind.span()); + let parser = Parser { kind, func }; + Sp::new(parser, parse_ident.span()) } } @@ -135,7 +202,7 @@ impl CasingStyle { } impl Attrs { - fn new(name: Sp, casing: Sp) -> Self { + fn new(default_span: Span, name: Sp, casing: Sp) -> Self { let cased_name = casing.translate(&name); Self { @@ -143,17 +210,14 @@ impl Attrs { cased_name, casing, methods: vec![], - parser: Sp::call_site(( - Sp::call_site(Parser::TryFromStr), - quote!(::std::str::FromStr::from_str), - )), + parser: Parser::default_spanned(default_span.clone()), about: None, author: None, version: None, no_version: None, has_custom_parser: false, - kind: Sp::call_site(Kind::Arg(Sp::call_site(Ty::Other))), + kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span), } } @@ -164,31 +228,15 @@ impl Attrs { self.cased_name = self.casing.translate(&arg); self.name = arg; } - _ => self.methods.push(Method { - name: name.as_ident(), - args: quote!(#arg), - }), + _ => self + .methods + .push(Method::new(name.as_ident(), quote!(#arg))), } } fn push_attrs(&mut self, attrs: &[Attribute]) { use crate::parse::StructOptAttr::*; - fn from_lit_or_env( - ident: Ident, - lit: Option, - 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) => { @@ -220,32 +268,26 @@ impl Attrs { NoVersion(ident) => self.no_version = Some(ident), About(ident, about) => { - self.about = from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION") + self.about = Method::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())) - }) + self.author = Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS"); } - Version(ident, version) => self.version = Some((ident, version)), + Version(ident, version) => { + self.version = Some(Method::new(ident, quote!(#version))) + } NameLitStr(name, lit) => { self.push_str_method(name.into(), lit.into()); } - NameExpr(name, expr) => self.methods.push(Method { - name: name.into(), - args: quote!(#expr), - }), + NameExpr(name, expr) => self.methods.push(Method::new(name.into(), quote!(#expr))), - MethodCall(name, args) => self.methods.push(Method { - name: name.into(), - args: quote!(#(#args),*), - }), + MethodCall(name, args) => self + .methods + .push(Method::new(name.into(), quote!(#(#args),*))), RenameAll(_, casing_lit) => { self.casing = CasingStyle::from_lit(casing_lit); @@ -254,37 +296,7 @@ impl Attrs { Parse(ident, spec) => { self.has_custom_parser = true; - - self.parser = match spec.parse_func { - None => { - use crate::attrs::Parser::*; - - let parser: Sp<_> = Parser::from_ident(spec.kind).into(); - let function = match *parser { - FromStr | FromOsStr => quote!(::std::convert::From::from), - TryFromStr => quote!(::std::str::FromStr::from_str), - TryFromOsStr => span_error!( - parser.span(), - "cannot omit parser function name with `try_from_os_str`" - ), - FromOccurrences => quote!({ |v| v as _ }), - }; - Sp::new((parser, function), ident.span()) - } - - Some(func) => { - let parser: Sp<_> = Parser::from_ident(spec.kind).into(); - match func { - syn::Expr::Path(_) => { - Sp::new((parser, quote!(#func)), ident.span()) - } - _ => span_error!( - func.span(), - "`parse` argument must be a function path" - ), - } - } - } + self.parser = Parser::from_spec(ident, spec); } } } @@ -351,10 +363,8 @@ impl Attrs { if expected_doc_comment_split { let long_name = Sp::call_site(format!("long_{}", name)); - self.methods.push(Method { - name: long_name.as_ident(), - args: quote!(#merged_lines), - }); + self.methods + .push(Method::new(long_name.as_ident(), quote!(#merged_lines))); // Remove trailing whitespace and period from short help, as rustdoc // best practice is to use complete sentences, but command-line help @@ -364,24 +374,25 @@ impl Attrs { .map(|s| s.trim()) .map_or("", |s| s.trim_end_matches('.')); - self.methods.push(Method { - name: Ident::new(name, Span::call_site()), - args: quote!(#short_arg), - }); + self.methods.push(Method::new( + Ident::new(name, Span::call_site()), + quote!(#short_arg), + )); } else { - self.methods.push(Method { - name: Ident::new(name, Span::call_site()), - args: quote!(#merged_lines), - }); + self.methods.push(Method::new( + Ident::new(name, Span::call_site()), + quote!(#merged_lines), + )); } } pub fn from_struct( + span: Span, attrs: &[Attribute], name: Sp, argument_casing: Sp, ) -> Self { - let mut res = Self::new(name, argument_casing); + let mut res = Self::new(span, name, argument_casing); res.push_attrs(attrs); res.push_doc_comment(attrs, "about"); @@ -431,7 +442,7 @@ impl Attrs { pub fn from_field(field: &syn::Field, struct_casing: Sp) -> Self { let name = field.ident.clone().unwrap(); - let mut res = Self::new(name.into(), struct_casing); + let mut res = Self::new(field.span(), name.into(), struct_casing); res.push_doc_comment(&field.attrs, "help"); res.push_attrs(&field.attrs); @@ -573,46 +584,25 @@ impl Attrs { "`no_version` and `version = \"version\"` can't be used together" ), - (None, Some((_, version))) => quote!(.version(#version)), + (None, Some(m)) => m.to_token_stream(), - (None, None) => { - if let Ok(version) = std::env::var("CARGO_PKG_VERSION") { - quote!(.version(#version)) - } else { - TokenStream::new() - } - } + (None, None) => std::env::var("CARGO_PKG_VERSION") + .map(|version| quote!( .version(#version) )) + .unwrap_or_default(), - (Some(_), None) => TokenStream::new(), + (Some(_), None) => quote!(), }; - 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); + let author = &self.author; + let about = &self.about; + let methods = &self.methods; - quote!( #(#methods)* ) + quote!( #(#methods)* #author #about #version ) } /// 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) )); + let methods = &self.methods; quote!( #(#methods)* ) } @@ -621,7 +611,7 @@ impl Attrs { self.cased_name.to_string() } - pub fn parser(&self) -> &(Sp, TokenStream) { + pub fn parser(&self) -> &Sp { &self.parser } @@ -657,3 +647,28 @@ pub fn sub_type(t: &syn::Type) -> Option<&syn::Type> { _ => None, } } + +/// replace all `:` with `, ` when not inside the `<>` +/// +/// `"author1:author2:author3" => "author1, author2, author3"` +/// `"author1 :author2" => "author1 , author2" +fn process_author_str(author: &str) -> String { + let mut res = String::with_capacity(author.len()); + let mut inside_angle_braces = 0usize; + + for ch in author.chars() { + if inside_angle_braces > 0 && ch == '>' { + inside_angle_braces -= 1; + res.push(ch); + } else if ch == '<' { + inside_angle_braces += 1; + res.push(ch); + } else if inside_angle_braces == 0 && ch == ':' { + res.push_str(", "); + } else { + res.push(ch); + } + } + + res +} diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 371c20ac..15ab7511 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -17,7 +17,7 @@ mod parse; mod spanned; use crate::{ - attrs::{sub_type, Attrs, CasingStyle, Kind, Parser, Ty}, + attrs::{sub_type, Attrs, CasingStyle, Kind, ParserKind, Ty}, spanned::Sp, }; @@ -58,7 +58,8 @@ fn gen_augmentation( ) -> TokenStream { let mut subcmds = fields.iter().filter_map(|field| { let attrs = Attrs::from_field(field, parent_attribute.casing()); - if let Kind::Subcommand(ty) = &*attrs.kind() { + let kind = attrs.kind(); + if let Kind::Subcommand(ty) = &*kind { let subcmd_type = match (**ty, sub_type(&field.ty)) { (Ty::Option, Some(sub_type)) => sub_type, _ => &field.ty, @@ -66,7 +67,7 @@ fn gen_augmentation( let required = if **ty == Ty::Option { quote!() } else { - quote! { + quote_spanned! { kind.span()=> let #app_var = #app_var.setting( ::structopt::clap::AppSettings::SubcommandRequiredElseHelp ); @@ -94,11 +95,12 @@ fn gen_augmentation( let args = fields.iter().filter_map(|field| { let attrs = Attrs::from_field(field, parent_attribute.casing()); - match &*attrs.kind() { + let kind = attrs.kind(); + match &*kind { Kind::Subcommand(_) | Kind::Skip => None, Kind::FlattenStruct => { let ty = &field.ty; - Some(quote! { + Some(quote_spanned! { kind.span()=> let #app_var = <#ty>::augment_clap(#app_var); let #app_var = if <#ty>::is_subcommand() { #app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp) @@ -110,48 +112,83 @@ fn gen_augmentation( Kind::Arg(ty) => { let convert_type = match **ty { Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), - Ty::OptionOption | Ty::OptionVec => sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty), + Ty::OptionOption | Ty::OptionVec => { + sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty) + } _ => &field.ty, }; - let occurrences = *attrs.parser().0 == Parser::FromOccurrences; + let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; - let (parser, f) = attrs.parser(); - let validator = match **parser { - Parser::TryFromStr => quote! { + let parser = attrs.parser(); + let func = &parser.func; + let validator = match *parser.kind { + ParserKind::TryFromStr => quote_spanned! { func.span()=> .validator(|s| { - #f(&s) + #func(&s) .map(|_: #convert_type| ()) .map_err(|e| e.to_string()) }) }, - Parser::TryFromOsStr => quote! { - .validator_os(|s| #f(&s).map(|_: #convert_type| ())) + ParserKind::TryFromOsStr => quote_spanned! { func.span()=> + .validator_os(|s| #func(&s).map(|_: #convert_type| ())) }, _ => quote!(), }; let modifier = match **ty { - Ty::Bool => quote!( .takes_value(false).multiple(false) ), - Ty::Option => quote!( .takes_value(true).multiple(false) #validator ), - Ty::OptionOption => { - quote! ( .takes_value(true).multiple(false).min_values(0).max_values(1) #validator ) - } - Ty::OptionVec => { - quote! ( .takes_value(true).multiple(true).min_values(0) #validator ) - } - Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ), - Ty::Other if occurrences => quote!( .takes_value(false).multiple(true) ), + Ty::Bool => quote_spanned! { ty.span()=> + .takes_value(false) + .multiple(false) + }, + + Ty::Option => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(false) + #validator + }, + + Ty::OptionOption => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(false) + .min_values(0) + .max_values(1) + #validator + }, + + Ty::OptionVec => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(true) + .min_values(0) + #validator + }, + + Ty::Vec => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(true) + #validator + }, + + Ty::Other if occurrences => quote_spanned! { ty.span()=> + .takes_value(false) + .multiple(true) + }, + Ty::Other => { let required = !attrs.has_method("default_value"); - quote!( .takes_value(true).multiple(false).required(#required) #validator ) + quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(false) + .required(#required) + #validator + } } }; let name = attrs.cased_name(); let methods = attrs.field_methods(); - Some(quote! { + Some(quote_spanned! { field.span()=> let #app_var = #app_var.arg( ::structopt::clap::Arg::with_name(#name) #modifier @@ -182,47 +219,72 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) }; let unwrapper = match **ty { Ty::Option => quote!(), - _ => quote!( .unwrap() ), + _ => quote_spanned!( ty.span()=> .unwrap() ), }; - quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper) + quote_spanned! { kind.span()=> + #field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper + } } - Kind::FlattenStruct => quote!(#field_name: ::structopt::StructOpt::from_clap(matches)), + + Kind::FlattenStruct => quote_spanned! { kind.span()=> + #field_name: ::structopt::StructOpt::from_clap(matches) + }, + Kind::Skip => quote_spanned!(kind.span()=> #field_name: Default::default()), + Kind::Arg(ty) => { - use crate::attrs::Parser::*; - let (parser, f) = attrs.parser(); - let (value_of, values_of, parse) = match **parser { - FromStr => (quote!(value_of), quote!(values_of), f.clone()), + use crate::attrs::ParserKind::*; + + let parser = attrs.parser(); + let func = &parser.func; + let span = parser.kind.span(); + let (value_of, values_of, parse) = match *parser.kind { + FromStr => ( + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + func.clone(), + ), TryFromStr => ( - quote!(value_of), - quote!(values_of), - quote!(|s| #f(s).unwrap()), + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + quote_spanned!(func.span()=> |s| #func(s).unwrap()), + ), + FromOsStr => ( + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + func.clone(), ), - FromOsStr => (quote!(value_of_os), quote!(values_of_os), f.clone()), TryFromOsStr => ( - quote!(value_of_os), - quote!(values_of_os), - quote!(|s| #f(s).unwrap()), + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + quote_spanned!(func.span()=> |s| #func(s).unwrap()), + ), + FromOccurrences => ( + quote_spanned!(span=> occurrences_of), + quote!(), + func.clone(), ), - FromOccurrences => (quote!(occurrences_of), quote!(), f.clone()), }; - let occurrences = *attrs.parser().0 == Parser::FromOccurrences; + let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; let name = attrs.cased_name(); let field_value = match **ty { - Ty::Bool => quote!(matches.is_present(#name)), - Ty::Option => quote! { + Ty::Bool => quote_spanned!(ty.span()=> matches.is_present(#name)), + + Ty::Option => quote_spanned! { ty.span()=> matches.#value_of(#name) .map(#parse) }, - Ty::OptionOption => quote! { + + Ty::OptionOption => quote_spanned! { ty.span()=> if matches.is_present(#name) { Some(matches.#value_of(#name).map(#parse)) } else { None } }, - Ty::OptionVec => quote! { + + Ty::OptionVec => quote_spanned! { ty.span()=> if matches.is_present(#name) { Some(matches.#values_of(#name) .map(|v| v.map(#parse).collect()) @@ -231,22 +293,25 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) None } }, - Ty::Vec => quote! { + + Ty::Vec => quote_spanned! { ty.span()=> matches.#values_of(#name) .map(|v| v.map(#parse).collect()) .unwrap_or_else(Vec::new) }, - Ty::Other if occurrences => quote! { + + Ty::Other if occurrences => quote_spanned! { ty.span()=> #parse(matches.#value_of(#name)) }, - Ty::Other => quote! { + + Ty::Other => quote_spanned! { ty.span()=> matches.#value_of(#name) .map(#parse) .unwrap() }, }; - quote!( #field_name: #field_value ) + quote_spanned!(field.span()=> #field_name: #field_value ) } } }); @@ -273,7 +338,12 @@ fn gen_from_clap( fn gen_clap(attrs: &[Attribute]) -> GenOutput { let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); - let attrs = Attrs::from_struct(attrs, Sp::call_site(name), Sp::call_site(DEFAULT_CASING)); + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Sp::call_site(name), + Sp::call_site(DEFAULT_CASING), + ); let tokens = { let name = attrs.cased_name(); let methods = attrs.top_level_methods(); @@ -339,6 +409,7 @@ fn gen_augment_clap_enum( let subcommands = variants.iter().map(|variant| { let attrs = Attrs::from_struct( + variant.span(), &variant.attrs, variant.ident.clone().into(), parent_attribute.casing(), @@ -349,7 +420,7 @@ fn gen_augment_clap_enum( Unit => quote!( #app_var ), Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; - quote! { + quote_spanned! { ty.span()=> { let #app_var = <#ty>::augment_clap(#app_var); if <#ty>::is_subcommand() { @@ -404,6 +475,7 @@ fn gen_from_subcommand( let match_arms = variants.iter().map(|variant| { let attrs = Attrs::from_struct( + variant.span(), &variant.attrs, variant.ident.clone().into(), parent_attribute.casing(), diff --git a/tests/doc-comments-help.rs b/tests/doc-comments-help.rs index 1c9e6b62..a99ba877 100644 --- a/tests/doc-comments-help.rs +++ b/tests/doc-comments-help.rs @@ -46,6 +46,10 @@ fn help_is_better_than_comments() { LoremIpsum::clap().write_long_help(&mut output).unwrap(); let output = String::from_utf8(output).unwrap(); + for line in output.split("\n") { + println!("{:#?}", line); + } + assert!(output.contains("Dolor sit amet")); assert!(!output.contains("Lorem ipsum")); assert!(output.contains("DO NOT PASS A BAR")); diff --git a/tests/ui-common/non_existent_attr.rs b/tests/ui-1.39_post/non_existent_attr.rs similarity index 100% rename from tests/ui-common/non_existent_attr.rs rename to tests/ui-1.39_post/non_existent_attr.rs diff --git a/tests/ui-1.39_post/non_existent_attr.stderr b/tests/ui-1.39_post/non_existent_attr.stderr new file mode 100644 index 00000000..77aeb22d --- /dev/null +++ b/tests/ui-1.39_post/non_existent_attr.stderr @@ -0,0 +1,7 @@ +error[E0599]: no method named `non_existing_attribute` found for type `clap::args::arg::Arg<'_, '_>` in the current scope + --> $DIR/non_existent_attr.rs:14:24 + | +14 | #[structopt(short, non_existing_attribute = 1)] + | ^^^^^^^^^^^^^^^^^^^^^^ method not found in `clap::args::arg::Arg<'_, '_>` + +For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui-pre_1.39/non_existent_attr.rs b/tests/ui-pre_1.39/non_existent_attr.rs new file mode 100644 index 00000000..96daf45c --- /dev/null +++ b/tests/ui-pre_1.39/non_existent_attr.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, non_existing_attribute = 1)] + debug: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/tests/ui-common/non_existent_attr.stderr b/tests/ui-pre_1.39/non_existent_attr.stderr similarity index 100% rename from tests/ui-common/non_existent_attr.stderr rename to tests/ui-pre_1.39/non_existent_attr.stderr