diff options
Diffstat (limited to 'third_party/rust/clap_derive/src')
-rw-r--r-- | third_party/rust/clap_derive/src/attrs.rs | 985 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/derives/arg_enum.rs | 123 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/derives/args.rs | 651 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/derives/into_app.rs | 119 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/derives/mod.rs | 23 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/derives/parser.rs | 95 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/derives/subcommand.rs | 666 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/dummies.rs | 91 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/lib.rs | 66 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/parse.rs | 279 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/utils/doc_comments.rs | 107 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/utils/mod.rs | 9 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/utils/spanned.rs | 92 | ||||
-rw-r--r-- | third_party/rust/clap_derive/src/utils/ty.rs | 121 |
14 files changed, 3427 insertions, 0 deletions
diff --git a/third_party/rust/clap_derive/src/attrs.rs b/third_party/rust/clap_derive/src/attrs.rs new file mode 100644 index 0000000000..aaee167e31 --- /dev/null +++ b/third_party/rust/clap_derive/src/attrs.rs @@ -0,0 +1,985 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use crate::{ + parse::*, + utils::{process_doc_comment, Sp, Ty}, +}; + +use std::env; + +use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use proc_macro2::{self, Span, TokenStream}; +use proc_macro_error::abort; +use quote::{quote, quote_spanned, ToTokens}; +use syn::{ + self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Ident, LitStr, MetaNameValue, + Type, Variant, +}; + +/// Default casing style for generated arguments. +pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; + +/// Default casing style for environment variables +pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake; + +#[derive(Clone)] +pub struct Attrs { + name: Name, + casing: Sp<CasingStyle>, + env_casing: Sp<CasingStyle>, + ty: Option<Type>, + doc_comment: Vec<Method>, + methods: Vec<Method>, + parser: Sp<Parser>, + verbatim_doc_comment: Option<Ident>, + next_display_order: Option<Method>, + next_help_heading: Option<Method>, + help_heading: Option<Method>, + is_enum: bool, + has_custom_parser: bool, + kind: Sp<Kind>, +} + +impl Attrs { + 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, None, 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::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"), + Kind::Arg(_) => res, + Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), + Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), + Kind::ExternalSubcommand => abort!( + res.kind.span(), + "external_subcommand is only allowed on fields" + ), + } + } + + pub fn from_variant( + variant: &Variant, + struct_casing: Sp<CasingStyle>, + env_casing: Sp<CasingStyle>, + ) -> Self { + let name = variant.ident.clone(); + let mut res = Self::new( + variant.span(), + Name::Derived(name), + None, + struct_casing, + env_casing, + ); + res.push_attrs(&variant.attrs); + res.push_doc_comment(&variant.attrs, "about"); + + match &*res.kind { + Kind::Flatten => { + if res.has_custom_parser { + abort!( + res.parser.span(), + "parse attribute is not allowed for flattened entry" + ); + } + if res.has_explicit_methods() { + abort!( + res.kind.span(), + "methods are not allowed for flattened entry" + ); + } + + // ignore doc comments + res.doc_comment = vec![]; + } + + Kind::ExternalSubcommand => (), + + Kind::Subcommand(_) => { + if res.has_custom_parser { + abort!( + res.parser.span(), + "parse attribute is not allowed for subcommand" + ); + } + + use syn::Fields::*; + use syn::FieldsUnnamed; + let field_ty = match variant.fields { + Named(_) => { + abort!(variant.span(), "structs are not allowed for subcommand"); + } + Unit => abort!(variant.span(), "unit-type is not allowed for subcommand"), + Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + &unnamed[0].ty + } + Unnamed(..) => { + abort!( + variant, + "non single-typed tuple is not allowed for subcommand" + ) + } + }; + let ty = Ty::from_syn_ty(field_ty); + match *ty { + Ty::OptionOption => { + abort!( + field_ty, + "Option<Option<T>> type is not allowed for subcommand" + ); + } + Ty::OptionVec => { + abort!( + field_ty, + "Option<Vec<T>> type is not allowed for subcommand" + ); + } + _ => (), + } + + res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span()); + } + Kind::Skip(_) => (), + Kind::FromGlobal(_) => { + abort!(res.kind.span(), "from_global is not supported on variants"); + } + Kind::Arg(_) => (), + } + + res + } + + pub fn from_arg_enum_variant( + variant: &Variant, + argument_casing: Sp<CasingStyle>, + env_casing: Sp<CasingStyle>, + ) -> Self { + let mut res = Self::new( + variant.span(), + Name::Derived(variant.ident.clone()), + None, + argument_casing, + env_casing, + ); + res.push_attrs(&variant.attrs); + res.push_doc_comment(&variant.attrs, "help"); + + 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::Skip(_) => res, + Kind::Arg(_) => res, + Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), + Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), + Kind::ExternalSubcommand => abort!( + res.kind.span(), + "external_subcommand is only allowed on fields" + ), + } + } + + pub fn from_field( + field: &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), + Some(field.ty.clone()), + struct_casing, + env_casing, + ); + res.push_attrs(&field.attrs); + res.push_doc_comment(&field.attrs, "help"); + + match &*res.kind { + Kind::Flatten => { + if res.has_custom_parser { + abort!( + res.parser.span(), + "parse attribute is not allowed for flattened entry" + ); + } + if res.has_explicit_methods() { + abort!( + res.kind.span(), + "methods are not allowed for flattened entry" + ); + } + + // ignore doc comments + res.doc_comment = vec![]; + } + + Kind::ExternalSubcommand => { + abort! { res.kind.span(), + "`external_subcommand` can be used only on enum variants" + } + } + + 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!( + field.ty, + "Option<Option<T>> type is not allowed for subcommand" + ); + } + Ty::OptionVec => { + abort!( + field.ty, + "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::FromGlobal(orig_ty) => { + let ty = Ty::from_syn_ty(&field.ty); + res.kind = Sp::new(Kind::FromGlobal(ty), orig_ty.span()); + } + 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!(field.ty, + "`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/clap-rs/clap/blob/master/examples/derive_ref/custom-bool.md"; + ) + } + if res.is_enum { + abort!(field.ty, "`arg_enum` is meaningless for bool") + } + if let Some(m) = res.find_default_method() { + abort!(m.name, "default_value is meaningless for bool") + } + if let Some(m) = res.find_method("required") { + abort!(m.name, "required is meaningless for bool") + } + } + Ty::Option => { + if let Some(m) = res.find_default_method() { + abort!(m.name, "default_value is meaningless for Option") + } + } + Ty::OptionOption => { + if res.is_positional() { + abort!( + field.ty, + "Option<Option<T>> type is meaningless for positional argument" + ) + } + } + Ty::OptionVec => { + if res.is_positional() { + abort!( + field.ty, + "Option<Vec<T>> type is meaningless for positional argument" + ) + } + } + + _ => (), + } + res.kind = Sp::new(Kind::Arg(ty), orig_ty.span()); + } + } + + res + } + + fn new( + default_span: Span, + name: Name, + ty: Option<Type>, + casing: Sp<CasingStyle>, + env_casing: Sp<CasingStyle>, + ) -> Self { + Self { + name, + ty, + casing, + env_casing, + doc_comment: vec![], + methods: vec![], + parser: Parser::default_spanned(default_span), + verbatim_doc_comment: None, + next_display_order: None, + next_help_heading: None, + help_heading: None, + is_enum: false, + has_custom_parser: false, + kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span), + } + } + + fn push_method(&mut self, name: Ident, arg: impl ToTokens) { + if name == "name" { + self.name = Name::Assigned(quote!(#arg)); + } else { + self.methods.push(Method::new(name, quote!(#arg))); + } + } + + fn push_attrs(&mut self, attrs: &[Attribute]) { + use ClapAttr::*; + + let parsed = parse_clap_attributes(attrs); + for attr in &parsed { + let attr = attr.clone(); + match attr { + Short(ident) => { + self.push_method(ident, self.name.clone().translate_char(*self.casing)); + } + + Long(ident) => { + self.push_method(ident, self.name.clone().translate(*self.casing)); + } + + Env(ident) => { + self.push_method(ident, self.name.clone().translate(*self.env_casing)); + } + + ArgEnum(_) => self.is_enum = true, + + FromGlobal(ident) => { + let ty = Sp::call_site(Ty::Other); + let kind = Sp::new(Kind::FromGlobal(ty), ident.span()); + self.set_kind(kind); + } + + Subcommand(ident) => { + let ty = Sp::call_site(Ty::Other); + let kind = Sp::new(Kind::Subcommand(ty), ident.span()); + self.set_kind(kind); + } + + ExternalSubcommand(ident) => { + let kind = Sp::new(Kind::ExternalSubcommand, ident.span()); + self.set_kind(kind); + } + + Flatten(ident) => { + let kind = Sp::new(Kind::Flatten, ident.span()); + self.set_kind(kind); + } + + Skip(ident, expr) => { + let kind = Sp::new(Kind::Skip(expr), ident.span()); + self.set_kind(kind); + } + + VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident), + + DefaultValueT(ident, expr) => { + let ty = if let Some(ty) = self.ty.as_ref() { + ty + } else { + abort!( + ident, + "#[clap(default_value_t)] (without an argument) can be used \ + only on field level"; + + note = "see \ + https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") + }; + + let val = if let Some(expr) = expr { + quote!(#expr) + } else { + quote!(<#ty as ::std::default::Default>::default()) + }; + + let val = if parsed.iter().any(|a| matches!(a, ArgEnum(_))) { + quote_spanned!(ident.span()=> { + { + let val: #ty = #val; + clap::ArgEnum::to_possible_value(&val).unwrap().get_name() + } + }) + } else { + quote_spanned!(ident.span()=> { + clap::lazy_static::lazy_static! { + static ref DEFAULT_VALUE: &'static str = { + let val: #ty = #val; + let s = ::std::string::ToString::to_string(&val); + ::std::boxed::Box::leak(s.into_boxed_str()) + }; + } + *DEFAULT_VALUE + }) + }; + + let raw_ident = Ident::new("default_value", ident.span()); + self.methods.push(Method::new(raw_ident, val)); + } + + DefaultValueOsT(ident, expr) => { + let ty = if let Some(ty) = self.ty.as_ref() { + ty + } else { + abort!( + ident, + "#[clap(default_value_os_t)] (without an argument) can be used \ + only on field level"; + + note = "see \ + https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") + }; + + let val = if let Some(expr) = expr { + quote!(#expr) + } else { + quote!(<#ty as ::std::default::Default>::default()) + }; + + let val = if parsed.iter().any(|a| matches!(a, ArgEnum(_))) { + quote_spanned!(ident.span()=> { + { + let val: #ty = #val; + clap::ArgEnum::to_possible_value(&val).unwrap().get_name() + } + }) + } else { + quote_spanned!(ident.span()=> { + clap::lazy_static::lazy_static! { + static ref DEFAULT_VALUE: &'static ::std::ffi::OsStr = { + let val: #ty = #val; + let s: ::std::ffi::OsString = val.into(); + ::std::boxed::Box::leak(s.into_boxed_os_str()) + }; + } + *DEFAULT_VALUE + }) + }; + + let raw_ident = Ident::new("default_value_os", ident.span()); + self.methods.push(Method::new(raw_ident, val)); + } + + NextDisplayOrder(ident, expr) => { + self.next_display_order = Some(Method::new(ident, quote!(#expr))); + } + + HelpHeading(ident, expr) => { + self.help_heading = Some(Method::new(ident, quote!(#expr))); + } + NextHelpHeading(ident, expr) => { + self.next_help_heading = Some(Method::new(ident, quote!(#expr))); + } + + About(ident) => { + if let Some(method) = Method::from_env(ident, "CARGO_PKG_DESCRIPTION") { + self.methods.push(method); + } + } + + Author(ident) => { + if let Some(method) = Method::from_env(ident, "CARGO_PKG_AUTHORS") { + self.methods.push(method); + } + } + + Version(ident) => { + if let Some(method) = Method::from_env(ident, "CARGO_PKG_VERSION") { + self.methods.push(method); + } + } + + NameLitStr(name, lit) => { + self.push_method(name, lit); + } + + NameExpr(name, expr) => { + self.push_method(name, expr); + } + + MethodCall(name, args) => self.push_method(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 syn::Lit::*; + use syn::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()); + } + + fn set_kind(&mut self, kind: Sp<Kind>) { + if let Kind::Arg(_) = *self.kind { + self.kind = kind; + } else { + abort!( + kind.span(), + "`subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together" + ); + } + } + + pub fn find_method(&self, name: &str) -> Option<&Method> { + self.methods.iter().find(|m| m.name == name) + } + + pub fn find_default_method(&self) -> Option<&Method> { + self.methods + .iter() + .find(|m| m.name == "default_value" || m.name == "default_value_os") + } + + /// generate methods from attributes on top of struct or enum + pub fn initial_top_level_methods(&self) -> TokenStream { + let next_display_order = self.next_display_order.as_ref().into_iter(); + let next_help_heading = self.next_help_heading.as_ref().into_iter(); + let help_heading = self.help_heading.as_ref().into_iter(); + quote!( + #(#next_display_order)* + #(#next_help_heading)* + #(#help_heading)* + ) + } + + pub fn final_top_level_methods(&self) -> TokenStream { + let methods = &self.methods; + let doc_comment = &self.doc_comment; + + quote!( #(#doc_comment)* #(#methods)*) + } + + /// generate methods on top of a field + pub fn field_methods(&self, supports_long_help: bool) -> proc_macro2::TokenStream { + let methods = &self.methods; + let help_heading = self.help_heading.as_ref().into_iter(); + match supports_long_help { + true => { + let doc_comment = &self.doc_comment; + quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* ) + } + false => { + let doc_comment = self + .doc_comment + .iter() + .filter(|mth| mth.name != "long_help"); + quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* ) + } + } + } + + pub fn next_display_order(&self) -> TokenStream { + let next_display_order = self.next_display_order.as_ref().into_iter(); + quote!( #(#next_display_order)* ) + } + + pub fn next_help_heading(&self) -> TokenStream { + let next_help_heading = self.next_help_heading.as_ref().into_iter(); + let help_heading = self.help_heading.as_ref().into_iter(); + quote!( #(#next_help_heading)* #(#help_heading)* ) + } + + #[cfg(feature = "unstable-v4")] + pub fn id(&self) -> TokenStream { + self.name.clone().raw() + } + + #[cfg(not(feature = "unstable-v4"))] + pub fn id(&self) -> TokenStream { + self.cased_name() + } + + pub fn cased_name(&self) -> TokenStream { + self.name.clone().translate(*self.casing) + } + + pub fn value_name(&self) -> TokenStream { + self.name.clone().translate(CasingStyle::ScreamingSnake) + } + + pub fn parser(&self) -> &Sp<Parser> { + &self.parser + } + + pub fn kind(&self) -> Sp<Kind> { + self.kind.clone() + } + + pub fn is_enum(&self) -> bool { + self.is_enum + } + + pub fn ignore_case(&self) -> TokenStream { + let method = self.find_method("ignore_case"); + + if let Some(method) = method { + method.args.clone() + } else { + quote! { false } + } + } + + 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") + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone)] +pub enum Kind { + Arg(Sp<Ty>), + FromGlobal(Sp<Ty>), + Subcommand(Sp<Ty>), + Flatten, + Skip(Option<Expr>), + ExternalSubcommand, +} + +#[derive(Clone)] +pub struct Method { + name: Ident, + args: TokenStream, +} + +impl Method { + pub fn new(name: Ident, args: TokenStream) -> Self { + Method { name, args } + } + + fn from_env(ident: Ident, env_var: &str) -> Option<Self> { + let mut lit = match env::var(env_var) { + Ok(val) => { + if val.is_empty() { + return None; + } + LitStr::new(&val, ident.span()) + } + Err(_) => { + abort!(ident, + "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 proc_macro2::TokenStream) { + let Method { ref name, ref args } = self; + + let tokens = quote!( .#name(#args) ); + + tokens.to_tokens(ts); + } +} + +/// 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 +} + +#[derive(Clone)] +pub struct Parser { + pub kind: Sp<ParserKind>, + pub func: TokenStream, +} + +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 self::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 { + Expr::Path(_) => quote!(#func), + _ => abort!(func, "`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()) + } +} + +#[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, + /// Keep all letters lowercase and remove word boundaries. + Lower, + /// Keep all letters uppercase and remove word boundaries. + Upper, + /// Use the original attribute name defined in the code. + Verbatim, +} + +impl CasingStyle { + fn from_lit(name: LitStr) -> Sp<Self> { + use self::CasingStyle::*; + + let normalized = name.value().to_upper_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), + "lower" | "lowercase" => cs(Lower), + "upper" | "uppercase" => cs(Upper), + "verbatim" | "verbatimcase" => cs(Verbatim), + s => abort!(name, "unsupported casing: `{}`", s), + } + } +} + +#[derive(Clone)] +pub enum Name { + Derived(Ident), + Assigned(TokenStream), +} + +impl Name { + #[cfg(feature = "unstable-v4")] + pub fn raw(self) -> TokenStream { + match self { + Name::Assigned(tokens) => tokens, + Name::Derived(ident) => { + let s = ident.unraw().to_string(); + quote_spanned!(ident.span()=> #s) + } + } + } + + pub fn translate(self, style: CasingStyle) -> TokenStream { + use CasingStyle::*; + + match self { + Name::Assigned(tokens) => tokens, + Name::Derived(ident) => { + let s = ident.unraw().to_string(); + let s = match style { + Pascal => s.to_upper_camel_case(), + Kebab => s.to_kebab_case(), + Camel => s.to_lower_camel_case(), + ScreamingSnake => s.to_shouty_snake_case(), + Snake => s.to_snake_case(), + Lower => s.to_snake_case().replace('_', ""), + Upper => s.to_shouty_snake_case().replace('_', ""), + Verbatim => s, + }; + quote_spanned!(ident.span()=> #s) + } + } + } + + pub fn translate_char(self, style: CasingStyle) -> TokenStream { + use CasingStyle::*; + + match self { + Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ), + Name::Derived(ident) => { + let s = ident.unraw().to_string(); + let s = match style { + Pascal => s.to_upper_camel_case(), + Kebab => s.to_kebab_case(), + Camel => s.to_lower_camel_case(), + ScreamingSnake => s.to_shouty_snake_case(), + Snake => s.to_snake_case(), + Lower => s.to_snake_case(), + Upper => s.to_shouty_snake_case(), + Verbatim => s, + }; + + let s = s.chars().next().unwrap(); + quote_spanned!(ident.span()=> #s) + } + } + } +} diff --git a/third_party/rust/clap_derive/src/derives/arg_enum.rs b/third_party/rust/clap_derive/src/derives/arg_enum.rs new file mode 100644 index 0000000000..77067e9029 --- /dev/null +++ b/third_party/rust/clap_derive/src/derives/arg_enum.rs @@ -0,0 +1,123 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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::{ + attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, + dummies, + utils::Sp, +}; + +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::{abort, abort_call_site}; +use quote::quote; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataEnum, DeriveInput, + Fields, Ident, Variant, +}; + +pub fn derive_arg_enum(input: &DeriveInput) -> TokenStream { + let ident = &input.ident; + + dummies::arg_enum(ident); + + match input.data { + Data::Enum(ref e) => gen_for_enum(ident, &input.attrs, e), + _ => abort_call_site!("`#[derive(ArgEnum)]` only supports enums"), + } +} + +pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream { + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + + let lits = lits(&e.variants, &attrs); + let value_variants = gen_value_variants(&lits); + let to_possible_value = gen_to_possible_value(&lits); + + quote! { + #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo, + clippy::suspicious_else_formatting, + )] + #[deny(clippy::correctness)] + impl clap::ArgEnum for #name { + #value_variants + #to_possible_value + } + } +} + +fn lits( + variants: &Punctuated<Variant, Comma>, + parent_attribute: &Attrs, +) -> Vec<(TokenStream, Ident)> { + variants + .iter() + .filter_map(|variant| { + let attrs = Attrs::from_arg_enum_variant( + variant, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + if let Kind::Skip(_) = &*attrs.kind() { + None + } else { + if !matches!(variant.fields, Fields::Unit) { + abort!(variant.span(), "`#[derive(ArgEnum)]` only supports non-unit variants, unless they are skipped"); + } + let fields = attrs.field_methods(false); + let name = attrs.cased_name(); + Some(( + quote! { + clap::PossibleValue::new(#name) + #fields + }, + variant.ident.clone(), + )) + } + }) + .collect::<Vec<_>>() +} + +fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream { + let lit = lits.iter().map(|l| &l.1).collect::<Vec<_>>(); + + quote! { + fn value_variants<'a>() -> &'a [Self]{ + &[#(Self::#lit),*] + } + } +} + +fn gen_to_possible_value(lits: &[(TokenStream, Ident)]) -> TokenStream { + let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip(); + + quote! { + fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::PossibleValue<'a>> { + match self { + #(Self::#variant => Some(#lit),)* + _ => None + } + } + } +} diff --git a/third_party/rust/clap_derive/src/derives/args.rs b/third_party/rust/clap_derive/src/derives/args.rs new file mode 100644 index 0000000000..e1486ea2bd --- /dev/null +++ b/third_party/rust/clap_derive/src/derives/args.rs @@ -0,0 +1,651 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use crate::{ + attrs::{Attrs, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING}, + dummies, + utils::{inner_type, sub_type, Sp, Ty}, +}; + +use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro_error::{abort, abort_call_site}; +use quote::{format_ident, quote, quote_spanned}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataStruct, + DeriveInput, Field, Fields, Generics, Type, +}; + +pub fn derive_args(input: &DeriveInput) -> TokenStream { + let ident = &input.ident; + + dummies::args(ident); + + match input.data { + Data::Struct(DataStruct { + fields: Fields::Named(ref fields), + .. + }) => gen_for_struct(ident, &input.generics, &fields.named, &input.attrs), + Data::Struct(DataStruct { + fields: Fields::Unit, + .. + }) => gen_for_struct( + ident, + &input.generics, + &Punctuated::<Field, Comma>::new(), + &input.attrs, + ), + _ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"), + } +} + +pub fn gen_for_struct( + struct_name: &Ident, + generics: &Generics, + fields: &Punctuated<Field, Comma>, + attrs: &[Attribute], +) -> TokenStream { + let from_arg_matches = gen_from_arg_matches_for_struct(struct_name, generics, fields, attrs); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(struct_name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let app_var = Ident::new("__clap_app", Span::call_site()); + let augmentation = gen_augment(fields, &app_var, &attrs, false); + let augmentation_update = gen_augment(fields, &app_var, &attrs, true); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + #from_arg_matches + + #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo, + clippy::suspicious_else_formatting, + )] + #[deny(clippy::correctness)] + impl #impl_generics clap::Args for #struct_name #ty_generics #where_clause { + fn augment_args<'b>(#app_var: clap::Command<'b>) -> clap::Command<'b> { + #augmentation + } + fn augment_args_for_update<'b>(#app_var: clap::Command<'b>) -> clap::Command<'b> { + #augmentation_update + } + } + } +} + +pub fn gen_from_arg_matches_for_struct( + struct_name: &Ident, + generics: &Generics, + fields: &Punctuated<Field, Comma>, + attrs: &[Attribute], +) -> TokenStream { + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(struct_name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + + let constructor = gen_constructor(fields, &attrs); + let updater = gen_updater(fields, &attrs, true); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo, + clippy::suspicious_else_formatting, + )] + #[deny(clippy::correctness)] + impl #impl_generics clap::FromArgMatches for #struct_name #ty_generics #where_clause { + fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> { + let v = #struct_name #constructor; + ::std::result::Result::Ok(v) + } + + fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> { + #updater + ::std::result::Result::Ok(()) + } + } + } +} + +/// Generate a block of code to add arguments/subcommands corresponding to +/// the `fields` to an cmd. +pub fn gen_augment( + fields: &Punctuated<Field, Comma>, + app_var: &Ident, + parent_attribute: &Attrs, + override_required: bool, +) -> 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()=> + #[allow(deprecated)] + let #app_var = #app_var.setting( + clap::AppSettings::SubcommandRequiredElseHelp + ); + } + }; + + let span = field.span(); + let ts = if override_required { + quote! { + let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands_for_update( #app_var ); + } + } else{ + quote! { + let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #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(_) + | Kind::FromGlobal(_) + | Kind::ExternalSubcommand => None, + Kind::Flatten => { + let ty = &field.ty; + let old_heading_var = format_ident!("__clap_old_heading"); + let next_help_heading = attrs.next_help_heading(); + let next_display_order = attrs.next_display_order(); + if override_required { + Some(quote_spanned! { kind.span()=> + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading #next_display_order; + let #app_var = <#ty as clap::Args>::augment_args_for_update(#app_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); + }) + } else { + Some(quote_spanned! { kind.span()=> + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading #next_display_order; + let #app_var = <#ty as clap::Args>::augment_args(#app_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); + }) + } + } + Kind::Arg(ty) => { + let convert_type = inner_type(**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 { + _ if attrs.is_enum() => quote!(), + ParserKind::TryFromStr => quote_spanned! { func.span()=> + .validator(|s| { + #func(s) + .map(|_: #convert_type| ()) + }) + }, + ParserKind::TryFromOsStr => quote_spanned! { func.span()=> + .validator_os(|s| #func(s).map(|_: #convert_type| ())) + }, + ParserKind::FromStr + | ParserKind::FromOsStr + | ParserKind::FromFlag + | ParserKind::FromOccurrences => quote!(), + }; + let allow_invalid_utf8 = match *parser.kind { + _ if attrs.is_enum() => quote!(), + ParserKind::FromOsStr | ParserKind::TryFromOsStr => { + quote_spanned! { func.span()=> + .allow_invalid_utf8(true) + } + } + ParserKind::FromStr + | ParserKind::TryFromStr + | ParserKind::FromFlag + | ParserKind::FromOccurrences => quote!(), + }; + + let value_name = attrs.value_name(); + let possible_values = if attrs.is_enum() { + gen_arg_enum_possible_values(convert_type) + } else { + quote!() + }; + + let modifier = match **ty { + Ty::Bool => quote!(), + + Ty::Option => { + quote_spanned! { ty.span()=> + .takes_value(true) + .value_name(#value_name) + #possible_values + #validator + #allow_invalid_utf8 + } + } + + Ty::OptionOption => quote_spanned! { ty.span()=> + .takes_value(true) + .value_name(#value_name) + .min_values(0) + .max_values(1) + .multiple_values(false) + #possible_values + #validator + #allow_invalid_utf8 + }, + + Ty::OptionVec => quote_spanned! { ty.span()=> + .takes_value(true) + .value_name(#value_name) + .multiple_occurrences(true) + #possible_values + #validator + #allow_invalid_utf8 + }, + + Ty::Vec => { + quote_spanned! { ty.span()=> + .takes_value(true) + .value_name(#value_name) + .multiple_occurrences(true) + #possible_values + #validator + #allow_invalid_utf8 + } + } + + Ty::Other if occurrences => quote_spanned! { ty.span()=> + .multiple_occurrences(true) + }, + + Ty::Other if flag => quote_spanned! { ty.span()=> + .takes_value(false) + }, + + Ty::Other => { + let required = attrs.find_default_method().is_none() && !override_required; + quote_spanned! { ty.span()=> + .takes_value(true) + .value_name(#value_name) + .required(#required) + #possible_values + #validator + #allow_invalid_utf8 + } + } + }; + + let id = attrs.id(); + let methods = attrs.field_methods(true); + + Some(quote_spanned! { field.span()=> + let #app_var = #app_var.arg( + clap::Arg::new(#id) + #modifier + #methods + ); + }) + } + } + }); + + let initial_app_methods = parent_attribute.initial_top_level_methods(); + let final_app_methods = parent_attribute.final_top_level_methods(); + quote! {{ + let #app_var = #app_var #initial_app_methods; + #( #args )* + #subcmd + #app_var #final_app_methods + }} +} + +fn gen_arg_enum_possible_values(ty: &Type) -> TokenStream { + quote_spanned! { ty.span()=> + .possible_values(<#ty as clap::ArgEnum>::value_variants().iter().filter_map(clap::ArgEnum::to_possible_value)) + } +} + +pub 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(); + let arg_matches = format_ident!("__clap_arg_matches"); + match &*kind { + Kind::ExternalSubcommand => { + abort! { kind.span(), + "`external_subcommand` can be used only on enum variants" + } + } + Kind::Subcommand(ty) => { + let subcmd_type = match (**ty, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty, + }; + match **ty { + Ty::Option => { + quote_spanned! { kind.span()=> + #field_name: { + if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) { + Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches(#arg_matches)?) + } else { + None + } + } + } + }, + _ => { + quote_spanned! { kind.span()=> + #field_name: { + <#subcmd_type as clap::FromArgMatches>::from_arg_matches(#arg_matches)? + } + } + }, + } + } + + Kind::Flatten => quote_spanned! { kind.span()=> + #field_name: clap::FromArgMatches::from_arg_matches(#arg_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) | Kind::FromGlobal(ty) => { + gen_parsers(&attrs, ty, field_name, field, None) + } + } + }); + + quote! {{ + #( #fields ),* + }} +} + +pub fn gen_updater( + fields: &Punctuated<Field, Comma>, + parent_attribute: &Attrs, + use_self: bool, +) -> 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(); + + let access = if use_self { + quote! { + #[allow(non_snake_case)] + let #field_name = &mut self.#field_name; + } + } else { + quote!() + }; + let arg_matches = format_ident!("__clap_arg_matches"); + + match &*kind { + Kind::ExternalSubcommand => { + abort! { kind.span(), + "`external_subcommand` can be used only on enum variants" + } + } + Kind::Subcommand(ty) => { + let subcmd_type = match (**ty, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty, + }; + + let updater = quote_spanned! { ty.span()=> + <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches(#field_name, #arg_matches)?; + }; + + let updater = match **ty { + Ty::Option => quote_spanned! { kind.span()=> + if let Some(#field_name) = #field_name.as_mut() { + #updater + } else { + *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches( + #arg_matches + )?); + } + }, + _ => quote_spanned! { kind.span()=> + #updater + }, + }; + + quote_spanned! { kind.span()=> + { + #access + #updater + } + } + } + + Kind::Flatten => quote_spanned! { kind.span()=> { + #access + clap::FromArgMatches::update_from_arg_matches(#field_name, #arg_matches)?; + } + }, + + Kind::Skip(_) => quote!(), + + Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(&attrs, ty, field_name, field, Some(&access)), + } + }); + + quote! { + #( #fields )* + } +} + +fn gen_parsers( + attrs: &Attrs, + ty: &Sp<Ty>, + field_name: &Ident, + field: &Field, + update: Option<&TokenStream>, +) -> TokenStream { + use self::ParserKind::*; + + let parser = attrs.parser(); + let func = &parser.func; + let span = parser.kind.span(); + let convert_type = inner_type(**ty, &field.ty); + let id = attrs.id(); + let (value_of, values_of, mut parse) = match *parser.kind { + FromStr => ( + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))), + ), + TryFromStr => ( + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))), + ), + FromOsStr => ( + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))), + ), + TryFromOsStr => ( + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))), + ), + FromOccurrences => ( + quote_spanned!(span=> occurrences_of), + quote!(), + func.clone(), + ), + FromFlag => (quote!(), quote!(), func.clone()), + }; + if attrs.is_enum() { + let ci = attrs.ignore_case(); + + parse = quote_spanned! { convert_type.span()=> + |s| <#convert_type as clap::ArgEnum>::from_str(s, #ci).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err))) + } + } + + let flag = *attrs.parser().kind == ParserKind::FromFlag; + let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; + // Give this identifier the same hygiene + // as the `arg_matches` parameter definition. This + // allows us to refer to `arg_matches` within a `quote_spanned` block + let arg_matches = format_ident!("__clap_arg_matches"); + + let field_value = match **ty { + Ty::Bool => { + if update.is_some() { + quote_spanned! { ty.span()=> + *#field_name || #arg_matches.is_present(#id) + } + } else { + quote_spanned! { ty.span()=> + #arg_matches.is_present(#id) + } + } + } + + Ty::Option => { + quote_spanned! { ty.span()=> + #arg_matches.#value_of(#id) + .map(#parse) + .transpose()? + } + } + + Ty::OptionOption => quote_spanned! { ty.span()=> + if #arg_matches.is_present(#id) { + Some(#arg_matches.#value_of(#id).map(#parse).transpose()?) + } else { + None + } + }, + + Ty::OptionVec => quote_spanned! { ty.span()=> + if #arg_matches.is_present(#id) { + Some(#arg_matches.#values_of(#id) + .map(|v| v.map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>()) + .transpose()? + .unwrap_or_else(Vec::new)) + } else { + None + } + }, + + Ty::Vec => { + quote_spanned! { ty.span()=> + #arg_matches.#values_of(#id) + .map(|v| v.map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>()) + .transpose()? + .unwrap_or_else(Vec::new) + } + } + + Ty::Other if occurrences => quote_spanned! { ty.span()=> + #parse(#arg_matches.#value_of(#id)) + }, + + Ty::Other if flag => quote_spanned! { ty.span()=> + #parse(#arg_matches.is_present(#id)) + }, + + Ty::Other => { + quote_spanned! { ty.span()=> + #arg_matches.#value_of(#id) + .ok_or_else(|| clap::Error::raw(clap::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id))) + .and_then(#parse)? + } + } + }; + + if let Some(access) = update { + quote_spanned! { field.span()=> + if #arg_matches.is_present(#id) { + #access + *#field_name = #field_value + } + } + } else { + quote_spanned!(field.span()=> #field_name: #field_value ) + } +} diff --git a/third_party/rust/clap_derive/src/derives/into_app.rs b/third_party/rust/clap_derive/src/derives/into_app.rs new file mode 100644 index 0000000000..3197357534 --- /dev/null +++ b/third_party/rust/clap_derive/src/derives/into_app.rs @@ -0,0 +1,119 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use std::env; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Attribute, Generics, Ident}; + +use crate::{ + attrs::{Attrs, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, + utils::Sp, +}; + +pub fn gen_for_struct( + struct_name: &Ident, + generics: &Generics, + attrs: &[Attribute], +) -> TokenStream { + let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Assigned(quote!(#app_name)), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let name = attrs.cased_name(); + let app_var = Ident::new("__clap_app", Span::call_site()); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let tokens = quote! { + #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo, + clippy::suspicious_else_formatting, + )] + #[deny(clippy::correctness)] + #[allow(deprecated)] + impl #impl_generics clap::CommandFactory for #struct_name #ty_generics #where_clause { + fn into_app<'b>() -> clap::Command<'b> { + let #app_var = clap::Command::new(#name); + <Self as clap::Args>::augment_args(#app_var) + } + + fn into_app_for_update<'b>() -> clap::Command<'b> { + let #app_var = clap::Command::new(#name); + <Self as clap::Args>::augment_args_for_update(#app_var) + } + } + }; + + tokens +} + +pub fn gen_for_enum(enum_name: &Ident, generics: &Generics, attrs: &[Attribute]) -> TokenStream { + let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Assigned(quote!(#app_name)), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let name = attrs.cased_name(); + let app_var = Ident::new("__clap_app", Span::call_site()); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo, + clippy::suspicious_else_formatting, + )] + #[deny(clippy::correctness)] + impl #impl_generics clap::CommandFactory for #enum_name #ty_generics #where_clause { + fn into_app<'b>() -> clap::Command<'b> { + #[allow(deprecated)] + let #app_var = clap::Command::new(#name) + .setting(clap::AppSettings::SubcommandRequiredElseHelp); + <Self as clap::Subcommand>::augment_subcommands(#app_var) + } + + fn into_app_for_update<'b>() -> clap::Command<'b> { + let #app_var = clap::Command::new(#name); + <Self as clap::Subcommand>::augment_subcommands_for_update(#app_var) + } + } + } +} diff --git a/third_party/rust/clap_derive/src/derives/mod.rs b/third_party/rust/clap_derive/src/derives/mod.rs new file mode 100644 index 0000000000..ee0cce56e7 --- /dev/null +++ b/third_party/rust/clap_derive/src/derives/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. +mod arg_enum; +mod args; +mod into_app; +mod parser; +mod subcommand; + +pub use self::parser::derive_parser; +pub use arg_enum::derive_arg_enum; +pub use args::derive_args; +pub use subcommand::derive_subcommand; diff --git a/third_party/rust/clap_derive/src/derives/parser.rs b/third_party/rust/clap_derive/src/derives/parser.rs new file mode 100644 index 0000000000..621430f310 --- /dev/null +++ b/third_party/rust/clap_derive/src/derives/parser.rs @@ -0,0 +1,95 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use crate::{ + derives::{args, into_app, subcommand}, + dummies, +}; + +use proc_macro2::TokenStream; +use proc_macro_error::abort_call_site; +use quote::quote; +use syn::{ + self, punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum, DataStruct, DeriveInput, + Field, Fields, Generics, Ident, +}; + +pub fn derive_parser(input: &DeriveInput) -> TokenStream { + let ident = &input.ident; + + match input.data { + Data::Struct(DataStruct { + fields: Fields::Named(ref fields), + .. + }) => { + dummies::parser_struct(ident); + gen_for_struct(ident, &input.generics, &fields.named, &input.attrs) + } + Data::Struct(DataStruct { + fields: Fields::Unit, + .. + }) => { + dummies::parser_struct(ident); + gen_for_struct( + ident, + &input.generics, + &Punctuated::<Field, Comma>::new(), + &input.attrs, + ) + } + Data::Enum(ref e) => { + dummies::parser_enum(ident); + gen_for_enum(ident, &input.generics, &input.attrs, e) + } + _ => abort_call_site!("`#[derive(Parser)]` only supports non-tuple structs and enums"), + } +} + +fn gen_for_struct( + name: &Ident, + generics: &Generics, + fields: &Punctuated<Field, Comma>, + attrs: &[Attribute], +) -> TokenStream { + let into_app = into_app::gen_for_struct(name, generics, attrs); + let args = args::gen_for_struct(name, generics, fields, attrs); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics clap::Parser for #name #ty_generics #where_clause {} + + #into_app + #args + } +} + +fn gen_for_enum( + name: &Ident, + generics: &Generics, + attrs: &[Attribute], + e: &DataEnum, +) -> TokenStream { + let into_app = into_app::gen_for_enum(name, generics, attrs); + let subcommand = subcommand::gen_for_enum(name, generics, attrs, e); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics clap::Parser for #name #ty_generics #where_clause {} + + #into_app + #subcommand + } +} diff --git a/third_party/rust/clap_derive/src/derives/subcommand.rs b/third_party/rust/clap_derive/src/derives/subcommand.rs new file mode 100644 index 0000000000..7d12347985 --- /dev/null +++ b/third_party/rust/clap_derive/src/derives/subcommand.rs @@ -0,0 +1,666 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. +use crate::{ + attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}, + derives::args, + dummies, + utils::{is_simple_ty, subty_if_name, Sp}, +}; + +use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro_error::{abort, abort_call_site}; +use quote::{format_ident, quote, quote_spanned}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, + FieldsUnnamed, Generics, Token, Variant, +}; + +pub fn derive_subcommand(input: &DeriveInput) -> TokenStream { + let ident = &input.ident; + + dummies::subcommand(ident); + + match input.data { + Data::Enum(ref e) => gen_for_enum(ident, &input.generics, &input.attrs, e), + _ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"), + } +} + +pub fn gen_for_enum( + enum_name: &Ident, + generics: &Generics, + attrs: &[Attribute], + e: &DataEnum, +) -> TokenStream { + let from_arg_matches = gen_from_arg_matches_for_enum(enum_name, generics, attrs, e); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(enum_name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let augmentation = gen_augment(&e.variants, &attrs, false); + let augmentation_update = gen_augment(&e.variants, &attrs, true); + let has_subcommand = gen_has_subcommand(&e.variants, &attrs); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + #from_arg_matches + + #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo, + clippy::suspicious_else_formatting, + )] + #[deny(clippy::correctness)] + impl #impl_generics clap::Subcommand for #enum_name #ty_generics #where_clause { + fn augment_subcommands <'b>(__clap_app: clap::Command<'b>) -> clap::Command<'b> { + #augmentation + } + fn augment_subcommands_for_update <'b>(__clap_app: clap::Command<'b>) -> clap::Command<'b> { + #augmentation_update + } + fn has_subcommand(__clap_name: &str) -> bool { + #has_subcommand + } + } + } +} + +fn gen_from_arg_matches_for_enum( + name: &Ident, + generics: &Generics, + attrs: &[Attribute], + e: &DataEnum, +) -> TokenStream { + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Derived(name.clone()), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + + let from_arg_matches = gen_from_arg_matches(name, &e.variants, &attrs); + let update_from_arg_matches = gen_update_from_arg_matches(name, &e.variants, &attrs); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] + #[allow( + clippy::style, + clippy::complexity, + clippy::pedantic, + clippy::restriction, + clippy::perf, + clippy::deprecated, + clippy::nursery, + clippy::cargo, + clippy::suspicious_else_formatting, + )] + #[deny(clippy::correctness)] + impl #impl_generics clap::FromArgMatches for #name #ty_generics #where_clause { + #from_arg_matches + #update_from_arg_matches + } + } +} + +fn gen_augment( + variants: &Punctuated<Variant, Token![,]>, + parent_attribute: &Attrs, + override_required: bool, +) -> TokenStream { + use syn::Fields::*; + + let app_var = Ident::new("__clap_app", Span::call_site()); + + let subcommands: Vec<_> = variants + .iter() + .filter_map(|variant| { + let attrs = Attrs::from_variant( + variant, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let kind = attrs.kind(); + + match &*kind { + Kind::Skip(_) => None, + + Kind::ExternalSubcommand => { + let ty = match variant.fields { + Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, + + _ => abort!( + variant, + "The enum variant marked with `external_subcommand` must be \ + a single-typed tuple, and the type must be either `Vec<String>` \ + or `Vec<OsString>`." + ), + }; + let subcommand = match subty_if_name(ty, "Vec") { + Some(subty) => { + if is_simple_ty(subty, "OsString") { + quote_spanned! { kind.span()=> + let #app_var = #app_var.allow_external_subcommands(true).allow_invalid_utf8_for_external_subcommands(true); + } + } else { + quote_spanned! { kind.span()=> + let #app_var = #app_var.allow_external_subcommands(true); + } + } + } + + None => abort!( + ty.span(), + "The type must be `Vec<_>` \ + to be used with `external_subcommand`." + ), + }; + Some(subcommand) + } + + Kind::Flatten => match variant.fields { + Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + let ty = &unnamed[0]; + let old_heading_var = format_ident!("__clap_old_heading"); + let next_help_heading = attrs.next_help_heading(); + let next_display_order = attrs.next_display_order(); + let subcommand = if override_required { + quote! { + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading #next_display_order; + let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); + } + } else { + quote! { + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading #next_display_order; + let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); + } + }; + Some(subcommand) + } + _ => abort!( + variant, + "`flatten` is usable only with single-typed tuple variants" + ), + }, + + Kind::Subcommand(_) => { + let subcommand_var = Ident::new("__clap_subcommand", Span::call_site()); + let arg_block = match variant.fields { + Named(_) => { + abort!(variant, "non single-typed tuple enums are not supported") + } + Unit => quote!( #subcommand_var ), + Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + let ty = &unnamed[0]; + if override_required { + quote_spanned! { ty.span()=> + { + <#ty as clap::Subcommand>::augment_subcommands_for_update(#subcommand_var) + } + } + } else { + quote_spanned! { ty.span()=> + { + <#ty as clap::Subcommand>::augment_subcommands(#subcommand_var) + } + } + } + } + Unnamed(..) => { + abort!(variant, "non single-typed tuple enums are not supported") + } + }; + + let name = attrs.cased_name(); + let initial_app_methods = attrs.initial_top_level_methods(); + let final_from_attrs = attrs.final_top_level_methods(); + let subcommand = quote! { + let #app_var = #app_var.subcommand({ + let #subcommand_var = clap::Command::new(#name); + let #subcommand_var = #subcommand_var #initial_app_methods; + let #subcommand_var = #arg_block; + #[allow(deprecated)] + let #subcommand_var = #subcommand_var.setting(clap::AppSettings::SubcommandRequiredElseHelp); + #subcommand_var #final_from_attrs + }); + }; + Some(subcommand) + } + + _ => { + let subcommand_var = Ident::new("__clap_subcommand", Span::call_site()); + let sub_augment = match variant.fields { + Named(ref fields) => { + // Defer to `gen_augment` for adding cmd methods + args::gen_augment(&fields.named, &subcommand_var, &attrs, override_required) + } + Unit => { + let arg_block = quote!( #subcommand_var ); + let initial_app_methods = attrs.initial_top_level_methods(); + let final_from_attrs = attrs.final_top_level_methods(); + quote! { + let #subcommand_var = #subcommand_var #initial_app_methods; + let #subcommand_var = #arg_block; + #subcommand_var #final_from_attrs + } + }, + Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + let ty = &unnamed[0]; + let arg_block = if override_required { + quote_spanned! { ty.span()=> + { + <#ty as clap::Args>::augment_args_for_update(#subcommand_var) + } + } + } else { + quote_spanned! { ty.span()=> + { + <#ty as clap::Args>::augment_args(#subcommand_var) + } + } + }; + let initial_app_methods = attrs.initial_top_level_methods(); + let final_from_attrs = attrs.final_top_level_methods(); + quote! { + let #subcommand_var = #subcommand_var #initial_app_methods; + let #subcommand_var = #arg_block; + #subcommand_var #final_from_attrs + } + } + Unnamed(..) => { + abort!(variant, "non single-typed tuple enums are not supported") + } + }; + + let name = attrs.cased_name(); + let subcommand = quote! { + let #app_var = #app_var.subcommand({ + let #subcommand_var = clap::Command::new(#name); + #sub_augment + }); + }; + Some(subcommand) + } + } + }) + .collect(); + + let initial_app_methods = parent_attribute.initial_top_level_methods(); + let final_app_methods = parent_attribute.final_top_level_methods(); + quote! { + let #app_var = #app_var #initial_app_methods; + #( #subcommands )*; + #app_var #final_app_methods + } +} + +fn gen_has_subcommand( + variants: &Punctuated<Variant, Token![,]>, + parent_attribute: &Attrs, +) -> TokenStream { + use syn::Fields::*; + + let mut ext_subcmd = false; + + let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants + .iter() + .filter_map(|variant| { + let attrs = Attrs::from_variant( + variant, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + + if let Kind::ExternalSubcommand = &*attrs.kind() { + ext_subcmd = true; + None + } else { + Some((variant, attrs)) + } + }) + .partition(|(_, attrs)| { + let kind = attrs.kind(); + matches!(&*kind, Kind::Flatten) + }); + + let subcommands = variants.iter().map(|(_variant, attrs)| { + let sub_name = attrs.cased_name(); + quote! { + if #sub_name == __clap_name { + return true + } + } + }); + let child_subcommands = flatten_variants + .iter() + .map(|(variant, _attrs)| match variant.fields { + Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed[0]; + quote! { + if <#ty as clap::Subcommand>::has_subcommand(__clap_name) { + return true; + } + } + } + _ => abort!( + variant, + "`flatten` is usable only with single-typed tuple variants" + ), + }); + + if ext_subcmd { + quote! { true } + } else { + quote! { + #( #subcommands )* + + #( #child_subcommands )else* + + false + } + } +} + +fn gen_from_arg_matches( + name: &Ident, + variants: &Punctuated<Variant, Token![,]>, + parent_attribute: &Attrs, +) -> TokenStream { + use syn::Fields::*; + + let mut ext_subcmd = None; + + let subcommand_name_var = format_ident!("__clap_name"); + let sub_arg_matches_var = format_ident!("__clap_sub_arg_matches"); + let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants + .iter() + .filter_map(|variant| { + let attrs = Attrs::from_variant( + variant, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + + if let Kind::ExternalSubcommand = &*attrs.kind() { + if ext_subcmd.is_some() { + abort!( + attrs.kind().span(), + "Only one variant can be marked with `external_subcommand`, \ + this is the second" + ); + } + + let ty = match variant.fields { + Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, + + _ => abort!( + variant, + "The enum variant marked with `external_subcommand` must be \ + a single-typed tuple, and the type must be either `Vec<String>` \ + or `Vec<OsString>`." + ), + }; + + let (span, str_ty, values_of) = match subty_if_name(ty, "Vec") { + Some(subty) => { + if is_simple_ty(subty, "String") { + ( + subty.span(), + quote!(::std::string::String), + quote!(values_of), + ) + } else if is_simple_ty(subty, "OsString") { + ( + subty.span(), + quote!(::std::ffi::OsString), + quote!(values_of_os), + ) + } else { + abort!( + ty.span(), + "The type must be either `Vec<String>` or `Vec<OsString>` \ + to be used with `external_subcommand`." + ); + } + } + + None => abort!( + ty.span(), + "The type must be either `Vec<String>` or `Vec<OsString>` \ + to be used with `external_subcommand`." + ), + }; + + ext_subcmd = Some((span, &variant.ident, str_ty, values_of)); + None + } else { + Some((variant, attrs)) + } + }) + .partition(|(_, attrs)| { + let kind = attrs.kind(); + matches!(&*kind, Kind::Flatten) + }); + + let subcommands = variants.iter().map(|(variant, attrs)| { + let sub_name = attrs.cased_name(); + let variant_name = &variant.ident; + let constructor_block = match variant.fields { + Named(ref fields) => args::gen_constructor(&fields.named, attrs), + Unit => quote!(), + Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed[0]; + quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches(__clap_arg_matches)? ) ) + } + Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), + }; + + if cfg!(feature = "unstable-v4") { + quote! { + if #sub_name == #subcommand_name_var && !#sub_arg_matches_var.is_present("") { + return ::std::result::Result::Ok(#name :: #variant_name #constructor_block) + } + } + } else { + quote! { + if #sub_name == #subcommand_name_var { + return ::std::result::Result::Ok(#name :: #variant_name #constructor_block) + } + } + } + }); + let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| { + let variant_name = &variant.ident; + match variant.fields { + Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed[0]; + quote! { + if <#ty as clap::Subcommand>::has_subcommand(__clap_name) { + let __clap_res = <#ty as clap::FromArgMatches>::from_arg_matches(__clap_arg_matches)?; + return ::std::result::Result::Ok(#name :: #variant_name (__clap_res)); + } + } + } + _ => abort!( + variant, + "`flatten` is usable only with single-typed tuple variants" + ), + } + }); + + let wildcard = match ext_subcmd { + Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=> + ::std::result::Result::Ok(#name::#var_name( + ::std::iter::once(#str_ty::from(#subcommand_name_var)) + .chain( + #sub_arg_matches_var.#values_of("").into_iter().flatten().map(#str_ty::from) + ) + .collect::<::std::vec::Vec<_>>() + )) + }, + + None => quote! { + ::std::result::Result::Err(clap::Error::raw(clap::ErrorKind::UnrecognizedSubcommand, format!("The subcommand '{}' wasn't recognized", #subcommand_name_var))) + }, + }; + + quote! { + fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> { + if let Some((#subcommand_name_var, #sub_arg_matches_var)) = __clap_arg_matches.subcommand() { + { + let __clap_arg_matches = #sub_arg_matches_var; + #( #subcommands )* + } + + #( #child_subcommands )else* + + #wildcard + } else { + ::std::result::Result::Err(clap::Error::raw(clap::ErrorKind::MissingSubcommand, "A subcommand is required but one was not provided.")) + } + } + } +} + +fn gen_update_from_arg_matches( + name: &Ident, + variants: &Punctuated<Variant, Token![,]>, + parent_attribute: &Attrs, +) -> TokenStream { + use syn::Fields::*; + + let (flatten, variants): (Vec<_>, Vec<_>) = variants + .iter() + .filter_map(|variant| { + let attrs = Attrs::from_variant( + variant, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + + match &*attrs.kind() { + // Fallback to `from_arg_matches` + Kind::ExternalSubcommand => None, + _ => Some((variant, attrs)), + } + }) + .partition(|(_, attrs)| { + let kind = attrs.kind(); + matches!(&*kind, Kind::Flatten) + }); + + let subcommands = variants.iter().map(|(variant, attrs)| { + let sub_name = attrs.cased_name(); + let variant_name = &variant.ident; + let (pattern, updater) = match variant.fields { + Named(ref fields) => { + let (fields, update): (Vec<_>, Vec<_>) = fields + .named + .iter() + .map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let field_name = field.ident.as_ref().unwrap(); + ( + quote!( ref mut #field_name ), + args::gen_updater(&fields.named, &attrs, false), + ) + }) + .unzip(); + (quote!( { #( #fields, )* }), quote!( { #( #update )* } )) + } + Unit => (quote!(), quote!({})), + Unnamed(ref fields) => { + if fields.unnamed.len() == 1 { + ( + quote!((ref mut __clap_arg)), + quote!(clap::FromArgMatches::update_from_arg_matches( + __clap_arg, + __clap_sub_arg_matches + )?), + ) + } else { + abort_call_site!("{}: tuple enums are not supported", variant.ident) + } + } + }; + + quote! { + #name :: #variant_name #pattern if #sub_name == __clap_name => { + let __clap_arg_matches = __clap_sub_arg_matches; + #updater + } + } + }); + + let child_subcommands = flatten.iter().map(|(variant, _attrs)| { + let variant_name = &variant.ident; + match variant.fields { + Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed[0]; + quote! { + if <#ty as clap::Subcommand>::has_subcommand(__clap_name) { + if let #name :: #variant_name (child) = s { + <#ty as clap::FromArgMatches>::update_from_arg_matches(child, __clap_arg_matches)?; + return ::std::result::Result::Ok(()); + } + } + } + } + _ => abort!( + variant, + "`flatten` is usable only with single-typed tuple variants" + ), + } + }); + + quote! { + fn update_from_arg_matches<'b>( + &mut self, + __clap_arg_matches: &clap::ArgMatches, + ) -> ::std::result::Result<(), clap::Error> { + if let Some((__clap_name, __clap_sub_arg_matches)) = __clap_arg_matches.subcommand() { + match self { + #( #subcommands ),* + s => { + #( #child_subcommands )* + *s = <Self as clap::FromArgMatches>::from_arg_matches(__clap_arg_matches)?; + } + } + } + ::std::result::Result::Ok(()) + } + } +} diff --git a/third_party/rust/clap_derive/src/dummies.rs b/third_party/rust/clap_derive/src/dummies.rs new file mode 100644 index 0000000000..be1583062b --- /dev/null +++ b/third_party/rust/clap_derive/src/dummies.rs @@ -0,0 +1,91 @@ +//! Dummy implementations that we emit along with an error. + +use proc_macro2::Ident; +use proc_macro_error::append_dummy; +use quote::quote; + +pub fn parser_struct(name: &Ident) { + into_app(name); + args(name); + append_dummy(quote!( impl clap::Parser for #name {} )); +} + +pub fn parser_enum(name: &Ident) { + into_app(name); + subcommand(name); + append_dummy(quote!( impl clap::Parser for #name {} )); +} + +pub fn into_app(name: &Ident) { + append_dummy(quote! { + #[allow(deprecated)] + impl clap::CommandFactory for #name { + fn into_app<'b>() -> clap::Command<'b> { + unimplemented!() + } + fn into_app_for_update<'b>() -> clap::Command<'b> { + unimplemented!() + } + } + }); +} + +pub fn from_arg_matches(name: &Ident) { + append_dummy(quote! { + impl clap::FromArgMatches for #name { + fn from_arg_matches(_m: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> { + unimplemented!() + } + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error>{ + unimplemented!() + } + } + }); +} + +pub fn subcommand(name: &Ident) { + from_arg_matches(name); + append_dummy(quote! { + impl clap::Subcommand for #name { + fn augment_subcommands(_cmd: clap::Command<'_>) -> clap::Command<'_> { + unimplemented!() + } + fn augment_subcommands_for_update(_cmd: clap::Command<'_>) -> clap::Command<'_> { + unimplemented!() + } + fn has_subcommand(name: &str) -> bool { + unimplemented!() + } + } + }); +} + +pub fn args(name: &Ident) { + from_arg_matches(name); + append_dummy(quote! { + impl clap::Args for #name { + fn augment_args(_cmd: clap::Command<'_>) -> clap::Command<'_> { + unimplemented!() + } + fn augment_args_for_update(_cmd: clap::Command<'_>) -> clap::Command<'_> { + unimplemented!() + } + } + }); +} + +pub fn arg_enum(name: &Ident) { + append_dummy(quote! { + impl clap::ArgEnum for #name { + fn value_variants<'a>() -> &'a [Self]{ + unimplemented!() + } + fn from_str(_input: &str, _ignore_case: bool) -> ::std::result::Result<Self, String> { + unimplemented!() + } + fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::PossibleValue<'a>>{ + unimplemented!() + } + } + }) +} diff --git a/third_party/rust/clap_derive/src/lib.rs b/third_party/rust/clap_derive/src/lib.rs new file mode 100644 index 0000000000..973b61e177 --- /dev/null +++ b/third_party/rust/clap_derive/src/lib.rs @@ -0,0 +1,66 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, +// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and +// Ana Hobden (@hoverbear) <operator@hoverbear.org> +// +// 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +#![doc(html_logo_url = "https://raw.githubusercontent.com/clap-rs/clap/master/assets/clap.png")] +#![doc = include_str!("../README.md")] +#![forbid(unsafe_code)] + +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use syn::{parse_macro_input, DeriveInput}; + +mod attrs; +mod derives; +mod dummies; +mod parse; +mod utils; + +/// Generates the `ArgEnum` impl. +#[proc_macro_derive(ArgEnum, attributes(clap))] +#[proc_macro_error] +pub fn arg_enum(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + derives::derive_arg_enum(&input).into() +} + +/// Generates the `Parser` implementation. +/// +/// This is far less verbose than defining the `clap::Command` struct manually, +/// receiving an instance of `clap::ArgMatches` from conducting parsing, and then +/// implementing a conversion code to instantiate an instance of the user +/// context struct. +#[proc_macro_derive(Parser, attributes(clap, structopt))] +#[proc_macro_error] +pub fn parser(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + derives::derive_parser(&input).into() +} + +/// Generates the `Subcommand` impl. +#[proc_macro_derive(Subcommand, attributes(clap))] +#[proc_macro_error] +pub fn subcommand(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + derives::derive_subcommand(&input).into() +} + +/// Generates the `Args` impl. +#[proc_macro_derive(Args, attributes(clap))] +#[proc_macro_error] +pub fn args(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + derives::derive_args(&input).into() +} diff --git a/third_party/rust/clap_derive/src/parse.rs b/third_party/rust/clap_derive/src/parse.rs new file mode 100644 index 0000000000..2973cf670f --- /dev/null +++ b/third_party/rust/clap_derive/src/parse.rs @@ -0,0 +1,279 @@ +use std::iter::FromIterator; + +use proc_macro_error::{abort, ResultExt}; +use quote::ToTokens; +use syn::{ + self, parenthesized, + parse::{Parse, ParseBuffer, ParseStream}, + punctuated::Punctuated, + Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token, +}; + +pub fn parse_clap_attributes(all_attrs: &[Attribute]) -> Vec<ClapAttr> { + all_attrs + .iter() + .filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt")) + .flat_map(|attr| { + attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated) + .unwrap_or_abort() + }) + .collect() +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone)] +pub enum ClapAttr { + // single-identifier attributes + Short(Ident), + Long(Ident), + Env(Ident), + Flatten(Ident), + ArgEnum(Ident), + FromGlobal(Ident), + Subcommand(Ident), + VerbatimDocComment(Ident), + ExternalSubcommand(Ident), + About(Ident), + Author(Ident), + Version(Ident), + + // ident = "string literal" + 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), + DefaultValueT(Ident, Option<Expr>), + DefaultValueOsT(Ident, Option<Expr>), + NextDisplayOrder(Ident, Expr), + NextHelpHeading(Ident, Expr), + HelpHeading(Ident, Expr), + + // ident(arbitrary_expr,*) + MethodCall(Ident, Vec<Expr>), +} + +impl Parse for ClapAttr { + fn parse(input: ParseStream) -> syn::Result<Self> { + use self::ClapAttr::*; + + 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()?; + + match &*name_str { + "rename_all" => Ok(RenameAll(name, lit)), + "rename_all_env" => Ok(RenameAllEnv(name, lit)), + + "skip" => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Str(lit), + }; + let expr = Expr::Lit(expr); + Ok(Skip(name, Some(expr))) + } + + "next_display_order" => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Str(lit), + }; + let expr = Expr::Lit(expr); + Ok(NextDisplayOrder(name, expr)) + } + + "next_help_heading" => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Str(lit), + }; + let expr = Expr::Lit(expr); + Ok(NextHelpHeading(name, expr)) + } + "help_heading" => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Str(lit), + }; + let expr = Expr::Lit(expr); + Ok(HelpHeading(name, expr)) + } + + _ => Ok(NameLitStr(name, lit)), + } + } else { + match input.parse::<Expr>() { + Ok(expr) => match &*name_str { + "skip" => Ok(Skip(name, Some(expr))), + "default_value_t" => Ok(DefaultValueT(name, Some(expr))), + "default_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))), + "next_display_order" => Ok(NextDisplayOrder(name, expr)), + "next_help_heading" => Ok(NextHelpHeading(name, expr)), + "help_heading" => Ok(HelpHeading(name, expr)), + _ => Ok(NameExpr(name, expr)), + }, + + Err(_) => abort! { + assign_token, + "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, "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, + "`#[clap(raw(...))` attributes are removed, \ + 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)), + "arg_enum" => Ok(ArgEnum(name)), + "from_global" => Ok(FromGlobal(name)), + "subcommand" => Ok(Subcommand(name)), + "external_subcommand" => Ok(ExternalSubcommand(name)), + "verbatim_doc_comment" => Ok(VerbatimDocComment(name)), + + "default_value" => { + abort!(name, + "`#[clap(default_value)` attribute (without a value) has been replaced by `#[clap(default_value_t)]`."; + help = "Change the attribute to `#[clap(default_value_t)]`"; + ) + } + "default_value_t" => Ok(DefaultValueT(name, None)), + "default_value_os_t" => Ok(DefaultValueOsT(name, None)), + "about" => (Ok(About(name))), + "author" => (Ok(Author(name))), + "version" => Ok(Version(name)), + + "skip" => Ok(Skip(name, None)), + + _ => abort!(name, "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, + }) + } +} + +fn raw_method_suggestion(ts: ParseBuffer) -> String { + let do_parse = move || -> Result<(Ident, Punctuated<Expr, Token![,]>), syn::Error> { + let name = ts.parse()?; + let _eq: Token![=] = ts.parse()?; + let val: LitStr = ts.parse()?; + let exprs = val.parse_with(Punctuated::<Expr, Token![,]>::parse_terminated)?; + Ok((name, exprs)) + }; + + fn to_string<T: ToTokens>(val: &T) -> String { + val.to_token_stream() + .to_string() + .replace(' ', "") + .replace(',', ", ") + } + + if let Ok((name, exprs)) = do_parse() { + 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/Command::{}` method you \ + can do it like this: #[clap({}{})]", + name, name, suggestion + ) + } else { + "if you need to call some method from `clap::Arg/Command` \ + you should use raw method, see \ + https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#raw-attributes" + .into() + } +} diff --git a/third_party/rust/clap_derive/src/utils/doc_comments.rs b/third_party/rust/clap_derive/src/utils/doc_comments.rs new file mode 100644 index 0000000000..f0a5034d70 --- /dev/null +++ b/third_party/rust/clap_derive/src/utils/doc_comments.rs @@ -0,0 +1,107 @@ +//! The preprocessing we apply to doc comments. +//! +//! #[derive(Parser)] 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)), + Method::new(long_name, quote!(None)), + ] + } +} + +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/third_party/rust/clap_derive/src/utils/mod.rs b/third_party/rust/clap_derive/src/utils/mod.rs new file mode 100644 index 0000000000..77a467c754 --- /dev/null +++ b/third_party/rust/clap_derive/src/utils/mod.rs @@ -0,0 +1,9 @@ +mod doc_comments; +mod spanned; +mod ty; + +pub use self::{ + doc_comments::process_doc_comment, + spanned::Sp, + ty::{inner_type, is_simple_ty, sub_type, subty_if_name, Ty}, +}; diff --git a/third_party/rust/clap_derive/src/utils/spanned.rs b/third_party/rust/clap_derive/src/utils/spanned.rs new file mode 100644 index 0000000000..11415f6f0e --- /dev/null +++ b/third_party/rust/clap_derive/src/utils/spanned.rs @@ -0,0 +1,92 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; +use syn::LitStr; + +use std::ops::{Deref, DerefMut}; + +/// An entity with a span attached. +#[derive(Debug, Clone)] +pub struct Sp<T> { + val: T, + span: Span, +} + +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> 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 could come up with + let tt = self.val.to_token_stream().into_iter().map(|mut tt| { + tt.set_span(self.span); + tt + }); + + stream.extend(tt); + } +} diff --git a/third_party/rust/clap_derive/src/utils/ty.rs b/third_party/rust/clap_derive/src/utils/ty.rs new file mode 100644 index 0000000000..891aa37d68 --- /dev/null +++ b/third_party/rust/clap_derive/src/utils/ty.rs @@ -0,0 +1,121 @@ +//! Special types handling + +use super::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 self::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 inner_type(ty: Ty, field_ty: &syn::Type) -> &syn::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, + } +} + +pub fn sub_type(ty: &syn::Type) -> Option<&syn::Type> { + subty_if(ty, |_| true) +} + +fn only_last_segment(mut ty: &syn::Type) -> Option<&PathSegment> { + while let syn::Type::Group(syn::TypeGroup { elem, .. }) = ty { + ty = elem; + } + 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 + } + }) +} + +pub fn subty_if_name<'a>(ty: &'a syn::Type, name: &str) -> Option<&'a syn::Type> { + subty_if(ty, |seg| seg.ident == name) +} + +pub 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()) +} |