diff options
Diffstat (limited to 'structopt/structopt-derive/src')
-rw-r--r-- | structopt/structopt-derive/src/attrs.rs | 620 | ||||
-rw-r--r-- | structopt/structopt-derive/src/doc_comments.rs | 103 | ||||
-rw-r--r-- | structopt/structopt-derive/src/lib.rs | 667 | ||||
-rw-r--r-- | structopt/structopt-derive/src/parse.rs | 304 | ||||
-rw-r--r-- | structopt/structopt-derive/src/spanned.rs | 101 | ||||
-rw-r--r-- | structopt/structopt-derive/src/ty.rs | 108 |
6 files changed, 0 insertions, 1903 deletions
diff --git a/structopt/structopt-derive/src/attrs.rs b/structopt/structopt-derive/src/attrs.rs deleted file mode 100644 index ce684a2..0000000 --- a/structopt/structopt-derive/src/attrs.rs +++ /dev/null @@ -1,620 +0,0 @@ -// 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 crate::doc_comments::process_doc_comment; -use crate::{parse::*, spanned::Sp, ty::Ty}; - -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}; - -#[derive(Clone)] -pub enum Kind { - Arg(Sp<Ty>), - Subcommand(Sp<Ty>), - FlattenStruct, - Skip(Option<Expr>), -} - -#[derive(Clone)] -pub struct Method { - name: Ident, - args: TokenStream, -} - -#[derive(Clone)] -pub struct Parser { - pub kind: Sp<ParserKind>, - pub func: TokenStream, -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ParserKind { - FromStr, - TryFromStr, - FromOsStr, - TryFromOsStr, - FromOccurrences, - FromFlag, -} - -/// Defines the casing for the attributes long representation. -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum CasingStyle { - /// Indicate word boundaries with uppercase letter, excluding the first word. - Camel, - /// Keep all letters lowercase and indicate word boundaries with hyphens. - Kebab, - /// Indicate word boundaries with uppercase letter, including the first word. - Pascal, - /// Keep all letters uppercase and indicate word boundaries with underscores. - ScreamingSnake, - /// Keep all letters lowercase and indicate word boundaries with underscores. - Snake, - /// Use the original attribute name defined in the code. - Verbatim, -} - -#[derive(Clone)] -pub enum Name { - Derived(Ident), - Assigned(LitStr), -} - -#[derive(Clone)] -pub struct Attrs { - name: Name, - casing: Sp<CasingStyle>, - env_casing: Sp<CasingStyle>, - doc_comment: Vec<Method>, - methods: Vec<Method>, - parser: Sp<Parser>, - author: Option<Method>, - about: Option<Method>, - version: Option<Method>, - no_version: Option<Ident>, - verbatim_doc_comment: Option<Ident>, - has_custom_parser: bool, - kind: Sp<Kind>, -} - -impl Method { - pub fn new(name: Ident, args: TokenStream) -> Self { - Method { name, args } - } - - fn from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Option<Self> { - let mut lit = match lit { - Some(lit) => lit, - - None => match env::var(env_var) { - Ok(val) => LitStr::new(&val, ident.span()), - Err(_) => { - abort!(ident.span(), - "cannot derive `{}` from Cargo.toml", ident; - note = "`{}` environment variable is not set", env_var; - help = "use `{} = \"...\"` to set {} manually", 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<Self> { - let kind = Sp::new(ParserKind::TryFromStr, span); - 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<Self> { - 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, - "from_flag" => FromFlag, - s => abort!(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 => abort!( - spec.kind.span(), - "you must set parser for `try_from_os_str` explicitly" - ), - FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }), - FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from), - }, - - Some(func) => match func { - syn::Expr::Path(_) => quote!(#func), - _ => abort!(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()) - } -} - -impl CasingStyle { - fn from_lit(name: LitStr) -> Sp<Self> { - use CasingStyle::*; - - let normalized = name.value().to_camel_case().to_lowercase(); - let cs = |kind| Sp::new(kind, name.span()); - - match normalized.as_ref() { - "camel" | "camelcase" => cs(Camel), - "kebab" | "kebabcase" => cs(Kebab), - "pascal" | "pascalcase" => cs(Pascal), - "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake), - "snake" | "snakecase" => cs(Snake), - "verbatim" | "verbatimcase" => cs(Verbatim), - s => abort!(name.span(), "unsupported casing: `{}`", s), - } - } -} - -impl Name { - pub fn translate(self, style: CasingStyle) -> LitStr { - use CasingStyle::*; - - match self { - Name::Assigned(lit) => lit, - Name::Derived(ident) => { - let s = ident.unraw().to_string(); - let s = match style { - Pascal => s.to_camel_case(), - Kebab => s.to_kebab_case(), - Camel => s.to_mixed_case(), - ScreamingSnake => s.to_shouty_snake_case(), - Snake => s.to_snake_case(), - Verbatim => s, - }; - LitStr::new(&s, ident.span()) - } - } - } -} - -impl Attrs { - fn new( - default_span: Span, - name: Name, - casing: Sp<CasingStyle>, - env_casing: Sp<CasingStyle>, - ) -> Self { - Self { - name, - casing, - env_casing, - doc_comment: vec![], - methods: vec![], - parser: Parser::default_spanned(default_span), - about: None, - author: None, - version: None, - no_version: None, - verbatim_doc_comment: None, - - has_custom_parser: false, - kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span), - } - } - - /// 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()); - } else { - self.methods - .push(Method::new(name.as_ident(), quote!(#arg))) - } - } - - fn push_attrs(&mut self, attrs: &[Attribute]) { - use crate::parse::StructOptAttr::*; - - 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(), - ); - } - - Env(ident) => { - self.push_str_method( - ident.into(), - self.name.clone().translate(*self.env_casing).into(), - ); - } - - Subcommand(ident) => { - let ty = Sp::call_site(Ty::Other); - let kind = Sp::new(Kind::Subcommand(ty), ident.span()); - self.set_kind(kind); - } - - Flatten(ident) => { - let kind = Sp::new(Kind::FlattenStruct, ident.span()); - self.set_kind(kind); - } - - Skip(ident, expr) => { - let kind = Sp::new(Kind::Skip(expr), ident.span()); - self.set_kind(kind); - } - - NoVersion(ident) => self.no_version = Some(ident), - - VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident), - - About(ident, about) => { - self.about = Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION"); - } - - Author(ident, author) => { - self.author = Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS"); - } - - 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::new(name, quote!(#expr))), - - MethodCall(name, args) => self.methods.push(Method::new(name, quote!(#(#args),*))), - - RenameAll(_, casing_lit) => { - self.casing = CasingStyle::from_lit(casing_lit); - } - - RenameAllEnv(_, casing_lit) => { - self.env_casing = CasingStyle::from_lit(casing_lit); - } - - Parse(ident, spec) => { - self.has_custom_parser = true; - self.parser = Parser::from_spec(ident, spec); - } - } - } - } - - fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) { - use crate::Lit::*; - use crate::Meta::*; - - let comment_parts: Vec<_> = attrs - .iter() - .filter(|attr| attr.path.is_ident("doc")) - .filter_map(|attr| { - if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() { - Some(s.value()) - } else { - // non #[doc = "..."] attributes are not our concern - // we leave them for rustc to handle - None - } - }) - .collect(); - - self.doc_comment = - process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none()); - } - - pub fn from_struct( - span: Span, - attrs: &[Attribute], - name: Name, - argument_casing: Sp<CasingStyle>, - env_casing: Sp<CasingStyle>, - ) -> Self { - let mut res = Self::new(span, name, argument_casing, env_casing); - res.push_attrs(attrs); - res.push_doc_comment(attrs, "about"); - - if res.has_custom_parser { - abort!( - res.parser.span(), - "`parse` attribute is only allowed on fields" - ); - } - match &*res.kind { - Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), - Kind::FlattenStruct => abort!(res.kind.span(), "flatten is only allowed on fields"), - Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"), - Kind::Arg(_) => res, - } - } - - pub fn from_field( - field: &syn::Field, - struct_casing: Sp<CasingStyle>, - env_casing: Sp<CasingStyle>, - ) -> Self { - let name = field.ident.clone().unwrap(); - let mut res = Self::new( - field.span(), - Name::Derived(name.clone()), - struct_casing, - env_casing, - ); - res.push_doc_comment(&field.attrs, "help"); - res.push_attrs(&field.attrs); - - match &*res.kind { - Kind::FlattenStruct => { - if res.has_custom_parser { - abort!( - res.parser.span(), - "parse attribute is not allowed for flattened entry" - ); - } - if res.has_explicit_methods() || res.has_doc_methods() { - abort!( - res.kind.span(), - "methods and doc comments are not allowed for flattened entry" - ); - } - } - Kind::Subcommand(_) => { - if res.has_custom_parser { - abort!( - res.parser.span(), - "parse attribute is not allowed for subcommand" - ); - } - if res.has_explicit_methods() { - abort!( - res.kind.span(), - "methods in attributes are not allowed for subcommand" - ); - } - - let ty = Ty::from_syn_ty(&field.ty); - match *ty { - Ty::OptionOption => { - abort!( - ty.span(), - "Option<Option<T>> type is not allowed for subcommand" - ); - } - Ty::OptionVec => { - abort!( - ty.span(), - "Option<Vec<T>> type is not allowed for subcommand" - ); - } - _ => (), - } - - res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span()); - } - Kind::Skip(_) => { - if res.has_explicit_methods() { - abort!( - res.kind.span(), - "methods are not allowed for skipped fields" - ); - } - } - Kind::Arg(orig_ty) => { - let mut ty = Ty::from_syn_ty(&field.ty); - if res.has_custom_parser { - match *ty { - Ty::Option | Ty::Vec | Ty::OptionVec => (), - _ => ty = Sp::new(Ty::Other, ty.span()), - } - } - - match *ty { - Ty::Bool => { - if res.is_positional() && !res.has_custom_parser { - abort!(ty.span(), - "`bool` cannot be used as positional parameter with default parser"; - help = "if you want to create a flag add `long` or `short`"; - help = "If you really want a boolean parameter \ - add an explicit parser, for example `parse(try_from_str)`"; - note = "see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs"; - ) - } - if let Some(m) = res.find_method("default_value") { - abort!(m.name.span(), "default_value is meaningless for bool") - } - if let Some(m) = res.find_method("required") { - abort!(m.name.span(), "required is meaningless for bool") - } - } - Ty::Option => { - if let Some(m) = res.find_method("default_value") { - abort!(m.name.span(), "default_value is meaningless for Option") - } - if let Some(m) = res.find_method("required") { - abort!(m.name.span(), "required is meaningless for Option") - } - } - Ty::OptionOption => { - if res.is_positional() { - abort!( - ty.span(), - "Option<Option<T>> type is meaningless for positional argument" - ) - } - } - Ty::OptionVec => { - if res.is_positional() { - abort!( - ty.span(), - "Option<Vec<T>> type is meaningless for positional argument" - ) - } - } - - _ => (), - } - res.kind = Sp::new(Kind::Arg(ty), orig_ty.span()); - } - } - - res - } - - fn set_kind(&mut self, kind: Sp<Kind>) { - if let Kind::Arg(_) = *self.kind { - self.kind = kind; - } else { - abort!( - kind.span(), - "subcommand, flatten and skip cannot be used together" - ); - } - } - - pub fn has_method(&self, name: &str) -> bool { - self.find_method(name).is_some() - } - - pub fn find_method(&self, name: &str) -> Option<&Method> { - self.methods.iter().find(|m| m.name == name) - } - - /// 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)* ) - } - - /// generate methods on top of a field - pub fn field_methods(&self) -> TokenStream { - let methods = &self.methods; - let doc_comment = &self.doc_comment; - quote!( #(#doc_comment)* #(#methods)* ) - } - - pub fn cased_name(&self) -> LitStr { - self.name.clone().translate(*self.casing) - } - - pub fn parser(&self) -> &Sp<Parser> { - &self.parser - } - - pub fn kind(&self) -> Sp<Kind> { - self.kind.clone() - } - - pub fn casing(&self) -> Sp<CasingStyle> { - self.casing.clone() - } - - pub fn env_casing(&self) -> Sp<CasingStyle> { - self.env_casing.clone() - } - - pub fn is_positional(&self) -> bool { - self.methods - .iter() - .all(|m| m.name != "long" && m.name != "short") - } - - pub fn has_explicit_methods(&self) -> bool { - self.methods - .iter() - .any(|m| m.name != "help" && m.name != "long_help") - } - - pub fn has_doc_methods(&self) -> bool { - !self.doc_comment.is_empty() - || self.methods.iter().any(|m| { - m.name == "help" - || m.name == "long_help" - || m.name == "about" - || m.name == "long_about" - }) - } -} - -/// replace all `:` with `, ` when not inside the `<>` -/// -/// `"author1:author2:author3" => "author1, author2, author3"` -/// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, 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/structopt-derive/src/doc_comments.rs b/structopt/structopt-derive/src/doc_comments.rs deleted file mode 100644 index 06e1b14..0000000 --- a/structopt/structopt-derive/src/doc_comments.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! The preprocessing we apply to doc comments. -//! -//! structopt works in terms of "paragraphs". Paragraph is a sequence of -//! non-empty adjacent lines, delimited by sequences of blank (whitespace only) lines. - -use crate::attrs::Method; -use quote::{format_ident, quote}; -use std::iter; - -pub fn process_doc_comment(lines: Vec<String>, name: &str, preprocess: bool) -> Vec<Method> { - // multiline comments (`/** ... */`) may have LFs (`\n`) in them, - // we need to split so we could handle the lines correctly - // - // we also need to remove leading and trailing blank lines - let mut lines: Vec<&str> = lines - .iter() - .skip_while(|s| is_blank(s)) - .flat_map(|s| s.split('\n')) - .collect(); - - while let Some(true) = lines.last().map(|s| is_blank(s)) { - lines.pop(); - } - - // remove one leading space no matter what - for line in lines.iter_mut() { - if line.starts_with(' ') { - *line = &line[1..]; - } - } - - if lines.is_empty() { - return vec![]; - } - - let short_name = format_ident!("{}", name); - let long_name = format_ident!("long_{}", name); - - if let Some(first_blank) = lines.iter().position(|s| is_blank(s)) { - let (short, long) = if preprocess { - let paragraphs = split_paragraphs(&lines); - let short = paragraphs[0].clone(); - let long = paragraphs.join("\n\n"); - (remove_period(short), long) - } else { - let short = lines[..first_blank].join("\n"); - let long = lines.join("\n"); - (short, long) - }; - - vec![ - Method::new(short_name, quote!(#short)), - Method::new(long_name, quote!(#long)), - ] - } else { - let short = if preprocess { - let s = merge_lines(&lines); - remove_period(s) - } else { - lines.join("\n") - }; - - vec![Method::new(short_name, quote!(#short))] - } -} - -fn split_paragraphs(lines: &[&str]) -> Vec<String> { - let mut last_line = 0; - iter::from_fn(|| { - let slice = &lines[last_line..]; - let start = slice.iter().position(|s| !is_blank(s)).unwrap_or(0); - - let slice = &slice[start..]; - let len = slice - .iter() - .position(|s| is_blank(s)) - .unwrap_or(slice.len()); - - last_line += start + len; - - if len != 0 { - Some(merge_lines(&slice[..len])) - } else { - None - } - }) - .collect() -} - -fn remove_period(mut s: String) -> String { - if s.ends_with('.') && !s.ends_with("..") { - s.pop(); - } - s -} - -fn is_blank(s: &str) -> bool { - s.trim().is_empty() -} - -fn merge_lines(lines: &[&str]) -> String { - lines.iter().map(|s| s.trim()).collect::<Vec<_>>().join(" ") -} diff --git a/structopt/structopt-derive/src/lib.rs b/structopt/structopt-derive/src/lib.rs deleted file mode 100644 index 87eaf1f..0000000 --- a/structopt/structopt-derive/src/lib.rs +++ /dev/null @@ -1,667 +0,0 @@ -// 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. - -//! This crate is custom derive for `StructOpt`. It should not be used -//! directly. See [structopt documentation](https://docs.rs/structopt) -//! for the usage of `#[derive(StructOpt)]`. - -#![allow(clippy::large_enum_variant)] - -extern crate proc_macro; - -mod attrs; -mod doc_comments; -mod parse; -mod spanned; -mod ty; - -use crate::{ - attrs::{Attrs, CasingStyle, Kind, Name, ParserKind}, - spanned::Sp, - ty::{sub_type, Ty}, -}; - -use proc_macro2::{Span, TokenStream}; -use proc_macro_error::{abort, abort_call_site, proc_macro_error, set_dummy}; -use quote::{quote, quote_spanned}; -use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, *}; - -/// Default casing style for generated arguments. -const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; - -/// Default casing style for environment variables -const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake; - -/// Output for the `gen_xxx()` methods were we need more than a simple stream of tokens. -/// -/// The output of a generation method is not only the stream of new tokens but also the attribute -/// information of the current element. These attribute information may contain valuable information -/// for any kind of child arguments. -struct GenOutput { - tokens: TokenStream, - attrs: Attrs, -} - -/// Generates the `StructOpt` impl. -#[proc_macro_derive(StructOpt, attributes(structopt))] -#[proc_macro_error] -pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input: DeriveInput = syn::parse(input).unwrap(); - let gen = impl_structopt(&input); - gen.into() -} - -/// Generate a block of code to add arguments/subcommands corresponding to -/// the `fields` to an app. -fn gen_augmentation( - fields: &Punctuated<Field, Comma>, - app_var: &Ident, - parent_attribute: &Attrs, -) -> TokenStream { - let mut subcmds = fields.iter().filter_map(|field| { - let attrs = Attrs::from_field( - field, - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - 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, - }; - let required = if **ty == Ty::Option { - quote!() - } else { - quote_spanned! { kind.span()=> - let #app_var = #app_var.setting( - ::structopt::clap::AppSettings::SubcommandRequiredElseHelp - ); - } - }; - - let span = field.span(); - let ts = quote! { - let #app_var = <#subcmd_type as ::structopt::StructOptInternal>::augment_clap( - #app_var - ); - #required - }; - Some((span, ts)) - } else { - None - } - }); - - let subcmd = subcmds.next().map(|(_, ts)| ts); - if let Some((span, _)) = subcmds.next() { - abort!( - span, - "multiple subcommand sets are not allowed, that's the second" - ); - } - - let args = fields.iter().filter_map(|field| { - let attrs = Attrs::from_field( - field, - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let kind = attrs.kind(); - match &*kind { - Kind::Subcommand(_) | Kind::Skip(_) => None, - Kind::FlattenStruct => { - let ty = &field.ty; - Some(quote_spanned! { kind.span()=> - let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(#app_var); - let #app_var = if <#ty as ::structopt::StructOptInternal>::is_subcommand() { - #app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp) - } else { - #app_var - }; - }) - } - 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) - } - _ => &field.ty, - }; - - let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; - let flag = *attrs.parser().kind == ParserKind::FromFlag; - - let parser = attrs.parser(); - let func = &parser.func; - let validator = match *parser.kind { - ParserKind::TryFromStr => quote_spanned! { func.span()=> - .validator(|s| { - #func(s.as_str()) - .map(|_: #convert_type| ()) - .map_err(|e| e.to_string()) - }) - }, - ParserKind::TryFromOsStr => quote_spanned! { func.span()=> - .validator_os(|s| #func(&s).map(|_: #convert_type| ())) - }, - _ => quote!(), - }; - - let modifier = match **ty { - 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 if flag => quote_spanned! { ty.span()=> - .takes_value(false) - .multiple(false) - }, - - Ty::Other => { - let required = !attrs.has_method("default_value"); - quote_spanned! { ty.span()=> - .takes_value(true) - .multiple(false) - .required(#required) - #validator - } - } - }; - - let name = attrs.cased_name(); - let methods = attrs.field_methods(); - - Some(quote_spanned! { field.span()=> - let #app_var = #app_var.arg( - ::structopt::clap::Arg::with_name(#name) - #modifier - #methods - ); - }) - } - } - }); - - let app_methods = parent_attribute.top_level_methods(); - quote! {{ - let #app_var = #app_var#app_methods; - #( #args )* - #subcmd - #app_var - }} -} - -fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream { - let fields = fields.iter().map(|field| { - let attrs = Attrs::from_field( - field, - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let field_name = field.ident.as_ref().unwrap(); - let kind = attrs.kind(); - match &*kind { - Kind::Subcommand(ty) => { - let subcmd_type = match (**ty, sub_type(&field.ty)) { - (Ty::Option, Some(sub_type)) => sub_type, - _ => &field.ty, - }; - let unwrapper = match **ty { - Ty::Option => quote!(), - _ => quote_spanned!( ty.span()=> .unwrap() ), - }; - quote_spanned! { kind.span()=> - #field_name: <#subcmd_type as ::structopt::StructOptInternal>::from_subcommand( - matches.subcommand()) - #unwrapper - } - } - - Kind::FlattenStruct => quote_spanned! { kind.span()=> - #field_name: ::structopt::StructOpt::from_clap(matches) - }, - - Kind::Skip(val) => match val { - None => quote_spanned!(kind.span()=> #field_name: Default::default()), - Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()), - }, - - Kind::Arg(ty) => { - 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_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(), - ), - TryFromOsStr => ( - 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(), - ), - FromFlag => (quote!(), quote!(), func.clone()), - }; - - let flag = *attrs.parser().kind == ParserKind::FromFlag; - let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; - let name = attrs.cased_name(); - let field_value = match **ty { - 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_spanned! { ty.span()=> - if matches.is_present(#name) { - Some(matches.#value_of(#name).map(#parse)) - } else { - None - } - }, - - Ty::OptionVec => quote_spanned! { ty.span()=> - if matches.is_present(#name) { - Some(matches.#values_of(#name) - .map_or_else(Vec::new, |v| v.map(#parse).collect())) - } else { - None - } - }, - - Ty::Vec => quote_spanned! { ty.span()=> - matches.#values_of(#name) - .map_or_else(Vec::new, |v| v.map(#parse).collect()) - }, - - Ty::Other if occurrences => quote_spanned! { ty.span()=> - #parse(matches.#value_of(#name)) - }, - - Ty::Other if flag => quote_spanned! { ty.span()=> - #parse(matches.is_present(#name)) - }, - - Ty::Other => quote_spanned! { ty.span()=> - matches.#value_of(#name) - .map(#parse) - .unwrap() - }, - }; - - quote_spanned!(field.span()=> #field_name: #field_value ) - } - } - }); - - quote! {{ - #( #fields ),* - }} -} - -fn gen_from_clap( - struct_name: &Ident, - fields: &Punctuated<Field, Comma>, - parent_attribute: &Attrs, -) -> TokenStream { - let field_block = gen_constructor(fields, parent_attribute); - - quote! { - fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { - #struct_name #field_block - } - } -} - -fn gen_clap(attrs: &[Attribute]) -> GenOutput { - let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); - - let attrs = Attrs::from_struct( - Span::call_site(), - attrs, - Name::Assigned(LitStr::new(&name, Span::call_site())), - Sp::call_site(DEFAULT_CASING), - Sp::call_site(DEFAULT_ENV_CASING), - ); - let tokens = { - let name = attrs.cased_name(); - quote!(::structopt::clap::App::new(#name)) - }; - - GenOutput { tokens, attrs } -} - -fn gen_clap_struct(struct_attrs: &[Attribute]) -> GenOutput { - let initial_clap_app_gen = gen_clap(struct_attrs); - let clap_tokens = initial_clap_app_gen.tokens; - - let augmented_tokens = quote! { - fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { - let app = #clap_tokens; - <Self as ::structopt::StructOptInternal>::augment_clap(app) - } - }; - - GenOutput { - tokens: augmented_tokens, - attrs: initial_clap_app_gen.attrs, - } -} - -fn gen_augment_clap(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream { - let app_var = Ident::new("app", Span::call_site()); - let augmentation = gen_augmentation(fields, &app_var, parent_attribute); - quote! { - fn augment_clap<'a, 'b>( - #app_var: ::structopt::clap::App<'a, 'b> - ) -> ::structopt::clap::App<'a, 'b> { - #augmentation - } - } -} - -fn gen_clap_enum(enum_attrs: &[Attribute]) -> GenOutput { - let initial_clap_app_gen = gen_clap(enum_attrs); - let clap_tokens = initial_clap_app_gen.tokens; - - let tokens = quote! { - fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { - let app = #clap_tokens - .setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp); - <Self as ::structopt::StructOptInternal>::augment_clap(app) - } - }; - - GenOutput { - tokens, - attrs: initial_clap_app_gen.attrs, - } -} - -fn gen_augment_clap_enum( - variants: &Punctuated<Variant, Comma>, - parent_attribute: &Attrs, -) -> TokenStream { - use syn::Fields::*; - - let subcommands = variants.iter().map(|variant| { - let attrs = Attrs::from_struct( - variant.span(), - &variant.attrs, - Name::Derived(variant.ident.clone()), - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let app_var = Ident::new("subcommand", Span::call_site()); - let arg_block = match variant.fields { - Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs), - Unit => quote!( #app_var ), - Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { - let ty = &unnamed[0]; - quote_spanned! { ty.span()=> - { - let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap( - #app_var - ); - if <#ty as ::structopt::StructOptInternal>::is_subcommand() { - #app_var.setting( - ::structopt::clap::AppSettings::SubcommandRequiredElseHelp - ) - } else { - #app_var - } - } - } - } - Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), - }; - - let name = attrs.cased_name(); - let from_attrs = attrs.top_level_methods(); - - quote! { - .subcommand({ - let #app_var = ::structopt::clap::SubCommand::with_name(#name); - let #app_var = #arg_block; - #app_var#from_attrs - }) - } - }); - - let app_methods = parent_attribute.top_level_methods(); - - quote! { - fn augment_clap<'a, 'b>( - app: ::structopt::clap::App<'a, 'b> - ) -> ::structopt::clap::App<'a, 'b> { - app #app_methods #( #subcommands )* - } - } -} - -fn gen_from_clap_enum(name: &Ident) -> TokenStream { - quote! { - fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { - <#name as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand()) - .unwrap() - } - } -} - -fn gen_from_subcommand( - name: &Ident, - variants: &Punctuated<Variant, Comma>, - parent_attribute: &Attrs, -) -> TokenStream { - use syn::Fields::*; - - let match_arms = variants.iter().map(|variant| { - let attrs = Attrs::from_struct( - variant.span(), - &variant.attrs, - Name::Derived(variant.ident.clone()), - parent_attribute.casing(), - parent_attribute.env_casing(), - ); - let sub_name = attrs.cased_name(); - let variant_name = &variant.ident; - let constructor_block = match variant.fields { - Named(ref fields) => gen_constructor(&fields.named, &attrs), - Unit => quote!(), - Unnamed(ref fields) if fields.unnamed.len() == 1 => { - let ty = &fields.unnamed[0]; - quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) ) - } - Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), - }; - - quote! { - (#sub_name, Some(matches)) => - Some(#name :: #variant_name #constructor_block) - } - }); - - quote! { - fn from_subcommand<'a, 'b>( - sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>) - ) -> Option<Self> { - match sub { - #( #match_arms ),*, - _ => None - } - } - } -} - -#[cfg(feature = "paw")] -fn gen_paw_impl(name: &Ident) -> TokenStream { - quote! { - impl paw::ParseArgs for #name { - type Error = std::io::Error; - - fn parse_args() -> std::result::Result<Self, Self::Error> { - Ok(<#name as ::structopt::StructOpt>::from_args()) - } - } - } -} -#[cfg(not(feature = "paw"))] -fn gen_paw_impl(_: &Ident) -> TokenStream { - TokenStream::new() -} - -fn impl_structopt_for_struct( - name: &Ident, - fields: &Punctuated<Field, Comma>, - attrs: &[Attribute], -) -> TokenStream { - let basic_clap_app_gen = gen_clap_struct(attrs); - let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs); - let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs); - let paw_impl = gen_paw_impl(name); - - let clap_tokens = basic_clap_app_gen.tokens; - quote! { - #[allow(unused_variables)] - #[allow(unknown_lints)] - #[allow(clippy::all)] - #[allow(dead_code, unreachable_code)] - impl ::structopt::StructOpt for #name { - #clap_tokens - #from_clap - } - - #[allow(unused_variables)] - #[allow(unknown_lints)] - #[allow(clippy::all)] - #[allow(dead_code, unreachable_code)] - impl ::structopt::StructOptInternal for #name { - #augment_clap - fn is_subcommand() -> bool { false } - } - - #paw_impl - } -} - -fn impl_structopt_for_enum( - name: &Ident, - variants: &Punctuated<Variant, Comma>, - attrs: &[Attribute], -) -> TokenStream { - let basic_clap_app_gen = gen_clap_enum(attrs); - - let augment_clap = gen_augment_clap_enum(variants, &basic_clap_app_gen.attrs); - let from_clap = gen_from_clap_enum(name); - let from_subcommand = gen_from_subcommand(name, variants, &basic_clap_app_gen.attrs); - let paw_impl = gen_paw_impl(name); - - let clap_tokens = basic_clap_app_gen.tokens; - quote! { - #[allow(unknown_lints)] - #[allow(unused_variables, dead_code, unreachable_code)] - #[allow(clippy)] - impl ::structopt::StructOpt for #name { - #clap_tokens - #from_clap - } - - #[allow(unused_variables)] - #[allow(unknown_lints)] - #[allow(clippy::all)] - #[allow(dead_code, unreachable_code)] - impl ::structopt::StructOptInternal for #name { - #augment_clap - #from_subcommand - fn is_subcommand() -> bool { true } - } - - #paw_impl - } -} - -fn impl_structopt(input: &DeriveInput) -> TokenStream { - use syn::Data::*; - - let struct_name = &input.ident; - - set_dummy(quote! { - impl ::structopt::StructOpt for #struct_name { - fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { - unimplemented!() - } - fn from_clap(_matches: &::structopt::clap::ArgMatches) -> Self { - unimplemented!() - } - } - }); - - match input.data { - Struct(DataStruct { - fields: syn::Fields::Named(ref fields), - .. - }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs), - Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs), - _ => abort_call_site!("structopt only supports non-tuple structs and enums"), - } -} diff --git a/structopt/structopt-derive/src/parse.rs b/structopt/structopt-derive/src/parse.rs deleted file mode 100644 index a704742..0000000 --- a/structopt/structopt-derive/src/parse.rs +++ /dev/null @@ -1,304 +0,0 @@ -use std::iter::FromIterator; - -use proc_macro_error::{abort, ResultExt}; -use quote::ToTokens; -use syn::{ - self, parenthesized, - parse::{Parse, ParseBuffer, ParseStream}, - parse2, - punctuated::Punctuated, - spanned::Spanned, - Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token, -}; - -pub struct StructOptAttributes { - pub paren_token: syn::token::Paren, - pub attrs: Punctuated<StructOptAttr, Token![,]>, -} - -impl Parse for StructOptAttributes { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - let content; - let paren_token = parenthesized!(content in input); - let attrs = content.parse_terminated(StructOptAttr::parse)?; - - Ok(StructOptAttributes { paren_token, attrs }) - } -} - -pub enum StructOptAttr { - // single-identifier attributes - Short(Ident), - Long(Ident), - Env(Ident), - Flatten(Ident), - Subcommand(Ident), - NoVersion(Ident), - VerbatimDocComment(Ident), - - // ident [= "string literal"] - About(Ident, Option<LitStr>), - Author(Ident, Option<LitStr>), - - // ident = "string literal" - Version(Ident, LitStr), - RenameAllEnv(Ident, LitStr), - RenameAll(Ident, LitStr), - NameLitStr(Ident, LitStr), - - // parse(parser_kind [= parser_func]) - Parse(Ident, ParserSpec), - - // ident [= arbitrary_expr] - Skip(Ident, Option<Expr>), - - // ident = arbitrary_expr - NameExpr(Ident, Expr), - - // ident(arbitrary_expr,*) - MethodCall(Ident, Vec<Expr>), -} - -impl Parse for StructOptAttr { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - use self::StructOptAttr::*; - - let name: Ident = input.parse()?; - let name_str = name.to_string(); - - if input.peek(Token![=]) { - // `name = value` attributes. - let assign_token = input.parse::<Token![=]>()?; // skip '=' - - if input.peek(LitStr) { - let lit: LitStr = input.parse()?; - let lit_str = lit.value(); - - let check_empty_lit = |s| { - if lit_str.is_empty() { - abort!( - lit.span(), - "`#[structopt({} = \"\")]` is deprecated in structopt 0.3, \ - now it's default behavior", - s - ); - } - }; - - match &*name_str.to_string() { - "rename_all" => Ok(RenameAll(name, lit)), - "rename_all_env" => Ok(RenameAllEnv(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))) - } - - "skip" => { - let expr = ExprLit { - attrs: vec![], - lit: Lit::Str(lit), - }; - let expr = Expr::Lit(expr); - Ok(Skip(name, Some(expr))) - } - - _ => Ok(NameLitStr(name, lit)), - } - } else { - match input.parse::<Expr>() { - Ok(expr) => { - if name_str == "skip" { - Ok(Skip(name, Some(expr))) - } else { - Ok(NameExpr(name, expr)) - } - } - - Err(_) => abort! { - assign_token.span(), - "expected `string literal` or `expression` after `=`" - }, - } - } - } else if input.peek(syn::token::Paren) { - // `name(...)` attributes. - let nested; - parenthesized!(nested in input); - - match name_str.as_ref() { - "parse" => { - let parser_specs: Punctuated<ParserSpec, Token![,]> = - nested.parse_terminated(ParserSpec::parse)?; - - if parser_specs.len() == 1 { - Ok(Parse(name, parser_specs[0].clone())) - } else { - abort!(name.span(), "parse must have exactly one argument") - } - } - - "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(_) => { - abort!(name.span(), - "`#[structopt(raw(...))` attributes are removed in structopt 0.3, \ - they are replaced with raw methods"; - help = "if you meant to call `clap::Arg::raw()` method \ - you should use bool literal, like `raw(true)` or `raw(false)`"; - note = raw_method_suggestion(nested); - ); - } - }, - - _ => { - let method_args: Punctuated<_, Token![,]> = - nested.parse_terminated(Expr::parse)?; - Ok(MethodCall(name, Vec::from_iter(method_args))) - } - } - } else { - // Attributes represented with a sole identifier. - match name_str.as_ref() { - "long" => Ok(Long(name)), - "short" => Ok(Short(name)), - "env" => Ok(Env(name)), - "flatten" => Ok(Flatten(name)), - "subcommand" => Ok(Subcommand(name)), - "no_version" => Ok(NoVersion(name)), - "verbatim_doc_comment" => Ok(VerbatimDocComment(name)), - - "about" => (Ok(About(name, None))), - "author" => (Ok(Author(name, None))), - - "skip" => Ok(Skip(name, None)), - - "version" => abort!( - name.span(), - "#[structopt(version)] is invalid attribute, \ - structopt 0.3 inherits version from Cargo.toml by default, \ - no attribute needed" - ), - - _ => abort!(name.span(), "unexpected attribute: {}", name_str), - } - } - } -} - -#[derive(Clone)] -pub struct ParserSpec { - pub kind: Ident, - pub eq_token: Option<Token![=]>, - pub parse_func: Option<Expr>, -} - -impl Parse for ParserSpec { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - let kind = input - .parse() - .map_err(|_| input.error("parser specification must start with identifier"))?; - let eq_token = input.parse()?; - let parse_func = match eq_token { - None => None, - Some(_) => Some(input.parse()?), - }; - Ok(ParserSpec { - kind, - eq_token, - parse_func, - }) - } -} - -struct CommaSeparated<T>(Punctuated<T, Token![,]>); - -impl<T: Parse> Parse for CommaSeparated<T> { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - let res = Punctuated::parse_separated_nonempty(input)?; - Ok(CommaSeparated(res)) - } -} - -fn raw_method_suggestion(ts: ParseBuffer) -> String { - let do_parse = move || -> Result<(Ident, CommaSeparated<Expr>), syn::Error> { - let name = ts.parse()?; - let _eq: Token![=] = ts.parse()?; - let val: LitStr = ts.parse()?; - Ok((name, syn::parse_str(&val.value())?)) - }; - - fn to_string<T: ToTokens>(val: &T) -> String { - val.to_token_stream() - .to_string() - .replace(" ", "") - .replace(",", ", ") - } - - if let Ok((name, val)) = do_parse() { - let exprs = val.0; - let suggestion = if exprs.len() == 1 { - let val = to_string(&exprs[0]); - format!(" = {}", val) - } else { - let val = exprs - .into_iter() - .map(|expr| to_string(&expr)) - .collect::<Vec<_>>() - .join(", "); - - format!("({})", val) - }; - - format!( - "if you need to call `clap::Arg/App::{}` method you \ - can do it like this: #[structopt({}{})]", - name, name, suggestion - ) - } else { - "if you need to call some method from `clap::Arg/App` \ - you should use raw method, see \ - https://docs.rs/structopt/0.3/structopt/#raw-methods" - .into() - } -} - -pub fn parse_structopt_attributes(all_attrs: &[Attribute]) -> Vec<StructOptAttr> { - all_attrs - .iter() - .filter(|attr| attr.path.is_ident("structopt")) - .flat_map(|attr| { - let attrs: StructOptAttributes = parse2(attr.tokens.clone()) - .map_err(|e| match &*e.to_string() { - // this error message is misleading and points to Span::call_site() - // so we patch it with something meaningful - "unexpected end of input, expected parentheses" => { - let span = attr.path.span(); - let patch_msg = "expected parentheses after `structopt`"; - syn::Error::new(span, patch_msg) - } - _ => e, - }) - .unwrap_or_abort(); - attrs.attrs - }) - .collect() -} diff --git a/structopt/structopt-derive/src/spanned.rs b/structopt/structopt-derive/src/spanned.rs deleted file mode 100644 index 2dd595b..0000000 --- a/structopt/structopt-derive/src/spanned.rs +++ /dev/null @@ -1,101 +0,0 @@ -use proc_macro2::{Ident, Span, TokenStream}; -use quote::ToTokens; -use std::ops::{Deref, DerefMut}; -use syn::LitStr; - -/// An entity with a span attached. -#[derive(Debug, Clone)] -pub struct Sp<T> { - span: Span, - val: T, -} - -impl<T> Sp<T> { - pub fn new(val: T, span: Span) -> Self { - Sp { val, span } - } - - pub fn call_site(val: T) -> Self { - Sp { - val, - span: Span::call_site(), - } - } - - pub fn span(&self) -> Span { - self.span - } -} - -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; - - fn deref(&self) -> &T { - &self.val - } -} - -impl<T> DerefMut for Sp<T> { - fn deref_mut(&mut self) -> &mut T { - &mut self.val - } -} - -impl From<Ident> for Sp<String> { - fn from(ident: Ident) -> Self { - Sp { - val: ident.to_string(), - span: ident.span(), - } - } -} - -impl From<LitStr> for Sp<String> { - fn from(lit: LitStr) -> Self { - Sp { - val: lit.value(), - span: lit.span(), - } - } -} - -impl<'a> From<Sp<&'a str>> for Sp<String> { - fn from(sp: Sp<&'a str>) -> Self { - Sp::new(sp.val.into(), sp.span) - } -} - -impl<U, T: PartialEq<U>> PartialEq<U> for Sp<T> { - fn eq(&self, other: &U) -> bool { - self.val == *other - } -} - -impl<T: AsRef<str>> AsRef<str> for Sp<T> { - fn as_ref(&self) -> &str { - self.val.as_ref() - } -} - -impl<T: ToTokens> ToTokens for Sp<T> { - fn to_tokens(&self, stream: &mut TokenStream) { - // this is the simplest way out of correct ones to change span on - // arbitrary token tree I can come up with - let tt = self.val.to_token_stream().into_iter().map(|mut tt| { - tt.set_span(self.span.clone()); - tt - }); - - stream.extend(tt); - } -} diff --git a/structopt/structopt-derive/src/ty.rs b/structopt/structopt-derive/src/ty.rs deleted file mode 100644 index 06eb3ec..0000000 --- a/structopt/structopt-derive/src/ty.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Special types handling - -use crate::spanned::Sp; - -use syn::{ - spanned::Spanned, GenericArgument, Path, PathArguments, PathArguments::AngleBracketed, - PathSegment, Type, TypePath, -}; - -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Ty { - Bool, - Vec, - Option, - OptionOption, - OptionVec, - Other, -} - -impl Ty { - pub fn from_syn_ty(ty: &syn::Type) -> Sp<Self> { - use Ty::*; - let t = |kind| Sp::new(kind, ty.span()); - - if is_simple_ty(ty, "bool") { - t(Bool) - } else if is_generic_ty(ty, "Vec") { - t(Vec) - } else if let Some(subty) = subty_if_name(ty, "Option") { - if is_generic_ty(subty, "Option") { - t(OptionOption) - } else if is_generic_ty(subty, "Vec") { - t(OptionVec) - } else { - t(Option) - } - } else { - t(Other) - } - } -} - -pub fn sub_type(ty: &syn::Type) -> Option<&syn::Type> { - subty_if(ty, |_| true) -} - -fn only_last_segment(ty: &syn::Type) -> Option<&PathSegment> { - match ty { - Type::Path(TypePath { - qself: None, - path: - Path { - leading_colon: None, - segments, - }, - }) => only_one(segments.iter()), - - _ => None, - } -} - -fn subty_if<F>(ty: &syn::Type, f: F) -> Option<&syn::Type> -where - F: FnOnce(&PathSegment) -> bool, -{ - only_last_segment(ty) - .filter(|segment| f(segment)) - .and_then(|segment| { - if let AngleBracketed(args) = &segment.arguments { - only_one(args.args.iter()).and_then(|genneric| { - if let GenericArgument::Type(ty) = genneric { - Some(ty) - } else { - None - } - }) - } else { - None - } - }) -} - -fn subty_if_name<'a>(ty: &'a syn::Type, name: &str) -> Option<&'a syn::Type> { - subty_if(ty, |seg| seg.ident == name) -} - -fn is_simple_ty(ty: &syn::Type, name: &str) -> bool { - only_last_segment(ty) - .map(|segment| { - if let PathArguments::None = segment.arguments { - segment.ident == name - } else { - false - } - }) - .unwrap_or(false) -} - -fn is_generic_ty(ty: &syn::Type, name: &str) -> bool { - subty_if_name(ty, name).is_some() -} - -fn only_one<I, T>(mut iter: I) -> Option<T> -where - I: Iterator<Item = T>, -{ - iter.next().filter(|_| iter.next().is_none()) -} |