| // Copyright 2018 Guillaume Pinot (@TeXitoi) <[email protected]>, |
| // Kevin Knapp (@kbknapp) <[email protected]>, and |
| // Ana Hobden (@hoverbear) <[email protected]> |
| // |
| // 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 heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; |
| use proc_macro2::{self, Span, TokenStream}; |
| use quote::{format_ident, quote, quote_spanned, ToTokens}; |
| use syn::DeriveInput; |
| use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Field, Ident, LitStr, Type, Variant}; |
| |
| use crate::attr::*; |
| use crate::utils::{extract_doc_comment, format_doc_comment, inner_type, is_simple_ty, Sp, Ty}; |
| |
| /// 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 Item { |
| name: Name, |
| casing: Sp<CasingStyle>, |
| env_casing: Sp<CasingStyle>, |
| ty: Option<Type>, |
| doc_comment: Vec<Method>, |
| methods: Vec<Method>, |
| deprecations: Vec<Deprecation>, |
| value_parser: Option<ValueParser>, |
| action: Option<Action>, |
| verbatim_doc_comment: bool, |
| force_long_help: bool, |
| next_display_order: Option<Method>, |
| next_help_heading: Option<Method>, |
| is_enum: bool, |
| is_positional: bool, |
| skip_group: bool, |
| group_id: Name, |
| group_methods: Vec<Method>, |
| kind: Sp<Kind>, |
| } |
| |
| impl Item { |
| pub fn from_args_struct(input: &DeriveInput, name: Name) -> Result<Self, syn::Error> { |
| let ident = input.ident.clone(); |
| let span = input.ident.span(); |
| let attrs = &input.attrs; |
| let argument_casing = Sp::new(DEFAULT_CASING, span); |
| let env_casing = Sp::new(DEFAULT_ENV_CASING, span); |
| let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span); |
| |
| let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind); |
| let parsed_attrs = ClapAttr::parse_all(attrs)?; |
| res.infer_kind(&parsed_attrs)?; |
| res.push_attrs(&parsed_attrs)?; |
| res.push_doc_comment(attrs, "about", Some("long_about")); |
| |
| Ok(res) |
| } |
| |
| pub fn from_subcommand_enum(input: &DeriveInput, name: Name) -> Result<Self, syn::Error> { |
| let ident = input.ident.clone(); |
| let span = input.ident.span(); |
| let attrs = &input.attrs; |
| let argument_casing = Sp::new(DEFAULT_CASING, span); |
| let env_casing = Sp::new(DEFAULT_ENV_CASING, span); |
| let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span); |
| |
| let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind); |
| let parsed_attrs = ClapAttr::parse_all(attrs)?; |
| res.infer_kind(&parsed_attrs)?; |
| res.push_attrs(&parsed_attrs)?; |
| res.push_doc_comment(attrs, "about", Some("long_about")); |
| |
| Ok(res) |
| } |
| |
| pub fn from_value_enum(input: &DeriveInput, name: Name) -> Result<Self, syn::Error> { |
| let ident = input.ident.clone(); |
| let span = input.ident.span(); |
| let attrs = &input.attrs; |
| let argument_casing = Sp::new(DEFAULT_CASING, span); |
| let env_casing = Sp::new(DEFAULT_ENV_CASING, span); |
| let kind = Sp::new(Kind::Value, span); |
| |
| let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind); |
| let parsed_attrs = ClapAttr::parse_all(attrs)?; |
| res.infer_kind(&parsed_attrs)?; |
| res.push_attrs(&parsed_attrs)?; |
| // Ignoring `push_doc_comment` as there is no top-level clap builder to add documentation |
| // to |
| |
| if res.has_explicit_methods() { |
| abort!( |
| res.methods[0].name.span(), |
| "{} doesn't exist for `ValueEnum` enums", |
| res.methods[0].name |
| ); |
| } |
| |
| Ok(res) |
| } |
| |
| pub fn from_subcommand_variant( |
| variant: &Variant, |
| struct_casing: Sp<CasingStyle>, |
| env_casing: Sp<CasingStyle>, |
| ) -> Result<Self, syn::Error> { |
| let name = variant.ident.clone(); |
| let ident = variant.ident.clone(); |
| let span = variant.span(); |
| let ty = match variant.fields { |
| syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { |
| Ty::from_syn_ty(&unnamed[0].ty) |
| } |
| syn::Fields::Named(_) | syn::Fields::Unnamed(..) | syn::Fields::Unit => { |
| Sp::new(Ty::Other, span) |
| } |
| }; |
| let kind = Sp::new(Kind::Command(ty), span); |
| let mut res = Self::new( |
| Name::Derived(name), |
| ident, |
| None, |
| struct_casing, |
| env_casing, |
| kind, |
| ); |
| let parsed_attrs = ClapAttr::parse_all(&variant.attrs)?; |
| res.infer_kind(&parsed_attrs)?; |
| res.push_attrs(&parsed_attrs)?; |
| if matches!(&*res.kind, Kind::Command(_) | Kind::Subcommand(_)) { |
| res.push_doc_comment(&variant.attrs, "about", Some("long_about")); |
| } |
| |
| match &*res.kind { |
| Kind::Flatten(_) => { |
| if res.has_explicit_methods() { |
| abort!( |
| res.kind.span(), |
| "methods are not allowed for flattened entry" |
| ); |
| } |
| } |
| |
| Kind::Subcommand(_) |
| | Kind::ExternalSubcommand |
| | Kind::FromGlobal(_) |
| | Kind::Skip(_, _) |
| | Kind::Command(_) |
| | Kind::Value |
| | Kind::Arg(_) => (), |
| } |
| |
| Ok(res) |
| } |
| |
| pub fn from_value_enum_variant( |
| variant: &Variant, |
| argument_casing: Sp<CasingStyle>, |
| env_casing: Sp<CasingStyle>, |
| ) -> Result<Self, syn::Error> { |
| let ident = variant.ident.clone(); |
| let span = variant.span(); |
| let kind = Sp::new(Kind::Value, span); |
| let mut res = Self::new( |
| Name::Derived(variant.ident.clone()), |
| ident, |
| None, |
| argument_casing, |
| env_casing, |
| kind, |
| ); |
| let parsed_attrs = ClapAttr::parse_all(&variant.attrs)?; |
| res.infer_kind(&parsed_attrs)?; |
| res.push_attrs(&parsed_attrs)?; |
| if matches!(&*res.kind, Kind::Value) { |
| res.push_doc_comment(&variant.attrs, "help", None); |
| } |
| |
| Ok(res) |
| } |
| |
| pub fn from_args_field( |
| field: &Field, |
| struct_casing: Sp<CasingStyle>, |
| env_casing: Sp<CasingStyle>, |
| ) -> Result<Self, syn::Error> { |
| let name = field.ident.clone().unwrap(); |
| let ident = field.ident.clone().unwrap(); |
| let span = field.span(); |
| let ty = Ty::from_syn_ty(&field.ty); |
| let kind = Sp::new(Kind::Arg(ty), span); |
| let mut res = Self::new( |
| Name::Derived(name), |
| ident, |
| Some(field.ty.clone()), |
| struct_casing, |
| env_casing, |
| kind, |
| ); |
| let parsed_attrs = ClapAttr::parse_all(&field.attrs)?; |
| res.infer_kind(&parsed_attrs)?; |
| res.push_attrs(&parsed_attrs)?; |
| if matches!(&*res.kind, Kind::Arg(_)) { |
| res.push_doc_comment(&field.attrs, "help", Some("long_help")); |
| } |
| |
| match &*res.kind { |
| Kind::Flatten(_) => { |
| if res.has_explicit_methods() { |
| abort!( |
| res.kind.span(), |
| "methods are not allowed for flattened entry" |
| ); |
| } |
| } |
| |
| Kind::Subcommand(_) => { |
| if res.has_explicit_methods() { |
| abort!( |
| res.kind.span(), |
| "methods in attributes are not allowed for subcommand" |
| ); |
| } |
| } |
| Kind::Skip(_, _) |
| | Kind::FromGlobal(_) |
| | Kind::Arg(_) |
| | Kind::Command(_) |
| | Kind::Value |
| | Kind::ExternalSubcommand => {} |
| } |
| |
| Ok(res) |
| } |
| |
| fn new( |
| name: Name, |
| ident: Ident, |
| ty: Option<Type>, |
| casing: Sp<CasingStyle>, |
| env_casing: Sp<CasingStyle>, |
| kind: Sp<Kind>, |
| ) -> Self { |
| let group_id = Name::Derived(ident); |
| Self { |
| name, |
| ty, |
| casing, |
| env_casing, |
| doc_comment: vec![], |
| methods: vec![], |
| deprecations: vec![], |
| value_parser: None, |
| action: None, |
| verbatim_doc_comment: false, |
| force_long_help: false, |
| next_display_order: None, |
| next_help_heading: None, |
| is_enum: false, |
| is_positional: true, |
| skip_group: false, |
| group_id, |
| group_methods: vec![], |
| kind, |
| } |
| } |
| |
| fn push_method(&mut self, kind: AttrKind, name: Ident, arg: impl ToTokens) { |
| self.push_method_(kind, name, arg.to_token_stream()); |
| } |
| |
| fn push_method_(&mut self, kind: AttrKind, name: Ident, arg: TokenStream) { |
| if name == "id" { |
| match kind { |
| AttrKind::Command | AttrKind::Value => { |
| self.deprecations.push(Deprecation { |
| span: name.span(), |
| id: "id_is_only_for_arg", |
| version: "4.0.0", |
| description: format!( |
| "`#[{}(id)] was allowed by mistake, instead use `#[{}(name)]`", |
| kind.as_str(), |
| kind.as_str() |
| ), |
| }); |
| self.name = Name::Assigned(arg); |
| } |
| AttrKind::Group => { |
| self.group_id = Name::Assigned(arg); |
| } |
| AttrKind::Arg | AttrKind::Clap | AttrKind::StructOpt => { |
| self.name = Name::Assigned(arg); |
| } |
| } |
| } else if name == "name" { |
| match kind { |
| AttrKind::Arg => { |
| self.deprecations.push(Deprecation { |
| span: name.span(), |
| id: "id_is_only_for_arg", |
| version: "4.0.0", |
| description: format!( |
| "`#[{}(name)] was allowed by mistake, instead use `#[{}(id)]` or `#[{}(value_name)]`", |
| kind.as_str(), |
| kind.as_str(), |
| kind.as_str() |
| ), |
| }); |
| self.name = Name::Assigned(arg); |
| } |
| AttrKind::Group => self.group_methods.push(Method::new(name, arg)), |
| AttrKind::Command | AttrKind::Value | AttrKind::Clap | AttrKind::StructOpt => { |
| self.name = Name::Assigned(arg); |
| } |
| } |
| } else if name == "value_parser" { |
| self.value_parser = Some(ValueParser::Explicit(Method::new(name, arg))); |
| } else if name == "action" { |
| self.action = Some(Action::Explicit(Method::new(name, arg))); |
| } else { |
| if name == "short" || name == "long" { |
| self.is_positional = false; |
| } |
| match kind { |
| AttrKind::Group => self.group_methods.push(Method::new(name, arg)), |
| _ => self.methods.push(Method::new(name, arg)), |
| }; |
| } |
| } |
| |
| fn infer_kind(&mut self, attrs: &[ClapAttr]) -> Result<(), syn::Error> { |
| for attr in attrs { |
| if let Some(AttrValue::Call(_)) = &attr.value { |
| continue; |
| } |
| |
| let actual_attr_kind = *attr.kind.get(); |
| let kind = match &attr.magic { |
| Some(MagicAttrName::FromGlobal) => { |
| if attr.value.is_some() { |
| let expr = attr.value_or_abort()?; |
| abort!(expr, "attribute `{}` does not accept a value", attr.name); |
| } |
| let ty = self |
| .kind() |
| .ty() |
| .cloned() |
| .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span())); |
| let kind = Sp::new(Kind::FromGlobal(ty), attr.name.clone().span()); |
| Some(kind) |
| } |
| Some(MagicAttrName::Subcommand) if attr.value.is_none() => { |
| if attr.value.is_some() { |
| let expr = attr.value_or_abort()?; |
| abort!(expr, "attribute `{}` does not accept a value", attr.name); |
| } |
| let ty = self |
| .kind() |
| .ty() |
| .cloned() |
| .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span())); |
| let kind = Sp::new(Kind::Subcommand(ty), attr.name.clone().span()); |
| Some(kind) |
| } |
| Some(MagicAttrName::ExternalSubcommand) if attr.value.is_none() => { |
| if attr.value.is_some() { |
| let expr = attr.value_or_abort()?; |
| abort!(expr, "attribute `{}` does not accept a value", attr.name); |
| } |
| let kind = Sp::new(Kind::ExternalSubcommand, attr.name.clone().span()); |
| Some(kind) |
| } |
| Some(MagicAttrName::Flatten) if attr.value.is_none() => { |
| if attr.value.is_some() { |
| let expr = attr.value_or_abort()?; |
| abort!(expr, "attribute `{}` does not accept a value", attr.name); |
| } |
| let ty = self |
| .kind() |
| .ty() |
| .cloned() |
| .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span())); |
| let kind = Sp::new(Kind::Flatten(ty), attr.name.clone().span()); |
| Some(kind) |
| } |
| Some(MagicAttrName::Skip) if actual_attr_kind != AttrKind::Group => { |
| let expr = attr.value.clone(); |
| let kind = Sp::new( |
| Kind::Skip(expr, self.kind.attr_kind()), |
| attr.name.clone().span(), |
| ); |
| Some(kind) |
| } |
| _ => None, |
| }; |
| |
| if let Some(kind) = kind { |
| self.set_kind(kind)?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn push_attrs(&mut self, attrs: &[ClapAttr]) -> Result<(), syn::Error> { |
| for attr in attrs { |
| let actual_attr_kind = *attr.kind.get(); |
| let expected_attr_kind = self.kind.attr_kind(); |
| match (actual_attr_kind, expected_attr_kind) { |
| (AttrKind::Clap, _) | (AttrKind::StructOpt, _) => { |
| self.deprecations.push(Deprecation::attribute( |
| "4.0.0", |
| actual_attr_kind, |
| expected_attr_kind, |
| attr.kind.span(), |
| )); |
| } |
| |
| (AttrKind::Group, AttrKind::Command) => {} |
| |
| _ if attr.kind != expected_attr_kind => { |
| abort!( |
| attr.kind.span(), |
| "Expected `{}` attribute instead of `{}`", |
| expected_attr_kind.as_str(), |
| actual_attr_kind.as_str() |
| ); |
| } |
| |
| _ => {} |
| } |
| |
| if let Some(AttrValue::Call(tokens)) = &attr.value { |
| // Force raw mode with method call syntax |
| self.push_method(*attr.kind.get(), attr.name.clone(), quote!(#(#tokens),*)); |
| continue; |
| } |
| |
| match &attr.magic { |
| Some(MagicAttrName::Short) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| self.push_method( |
| *attr.kind.get(), |
| attr.name.clone(), |
| self.name.clone().translate_char(*self.casing), |
| ); |
| } |
| |
| Some(MagicAttrName::Long) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| self.push_method(*attr.kind.get(), attr.name.clone(), self.name.clone().translate(*self.casing)); |
| } |
| |
| Some(MagicAttrName::ValueParser) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| self.deprecations.push(Deprecation { |
| span: attr.name.span(), |
| id: "bare_value_parser", |
| version: "4.0.0", |
| description: "`#[arg(value_parser)]` is now the default and is no longer needed`".to_owned(), |
| }); |
| self.value_parser = Some(ValueParser::Implicit(attr.name.clone())); |
| } |
| |
| Some(MagicAttrName::Action) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| self.deprecations.push(Deprecation { |
| span: attr.name.span(), |
| id: "bare_action", |
| version: "4.0.0", |
| description: "`#[arg(action)]` is now the default and is no longer needed`".to_owned(), |
| }); |
| self.action = Some(Action::Implicit(attr.name.clone())); |
| } |
| |
| Some(MagicAttrName::Env) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| self.push_method( |
| *attr.kind.get(), |
| attr.name.clone(), |
| self.name.clone().translate(*self.env_casing), |
| ); |
| } |
| |
| Some(MagicAttrName::ValueEnum) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| self.is_enum = true |
| } |
| |
| Some(MagicAttrName::VerbatimDocComment) if attr.value.is_none() => { |
| self.verbatim_doc_comment = true |
| } |
| |
| Some(MagicAttrName::About) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Command])?; |
| |
| if let Some(method) = |
| Method::from_env(attr.name.clone(), "CARGO_PKG_DESCRIPTION")? |
| { |
| self.methods.push(method); |
| } |
| } |
| |
| Some(MagicAttrName::LongAbout) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Command])?; |
| |
| self.force_long_help = true; |
| } |
| |
| Some(MagicAttrName::LongHelp) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| self.force_long_help = true; |
| } |
| |
| Some(MagicAttrName::Author) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Command])?; |
| |
| if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_AUTHORS")? { |
| self.methods.push(method); |
| } |
| } |
| |
| Some(MagicAttrName::Version) if attr.value.is_none() => { |
| assert_attr_kind(attr, &[AttrKind::Command])?; |
| |
| if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_VERSION")? { |
| self.methods.push(method); |
| } |
| } |
| |
| Some(MagicAttrName::DefaultValueT) => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| let ty = if let Some(ty) = self.ty.as_ref() { |
| ty |
| } else { |
| abort!( |
| attr.name.clone(), |
| "#[arg(default_value_t)] (without an argument) can be used \ |
| only on field level\n\n= note: {note}\n\n", |
| |
| note = "see \ |
| https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") |
| }; |
| |
| let val = if let Some(expr) = &attr.value { |
| quote!(#expr) |
| } else { |
| quote!(<#ty as ::std::default::Default>::default()) |
| }; |
| |
| let val = if attrs |
| .iter() |
| .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
| { |
| quote_spanned!(attr.name.clone().span()=> { |
| static DEFAULT_VALUE: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new(); |
| let s = DEFAULT_VALUE.get_or_init(|| { |
| let val: #ty = #val; |
| clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned() |
| }); |
| let s: &'static str = &*s; |
| s |
| }) |
| } else { |
| quote_spanned!(attr.name.clone().span()=> { |
| static DEFAULT_VALUE: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new(); |
| let s = DEFAULT_VALUE.get_or_init(|| { |
| let val: #ty = #val; |
| ::std::string::ToString::to_string(&val) |
| }); |
| let s: &'static str = &*s; |
| s |
| }) |
| }; |
| |
| let raw_ident = Ident::new("default_value", attr.name.clone().span()); |
| self.methods.push(Method::new(raw_ident, val)); |
| } |
| |
| Some(MagicAttrName::DefaultValuesT) => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| let ty = if let Some(ty) = self.ty.as_ref() { |
| ty |
| } else { |
| abort!( |
| attr.name.clone(), |
| "#[arg(default_values_t)] (without an argument) can be used \ |
| only on field level\n\n= note: {note}\n\n", |
| |
| note = "see \ |
| https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") |
| }; |
| let expr = attr.value_or_abort()?; |
| |
| let container_type = Ty::from_syn_ty(ty); |
| if *container_type != Ty::Vec { |
| abort!( |
| attr.name.clone(), |
| "#[arg(default_values_t)] can be used only on Vec types\n\n= note: {note}\n\n", |
| |
| note = "see \ |
| https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") |
| } |
| let inner_type = inner_type(ty); |
| |
| // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and |
| // `Vec<#inner_type>`. |
| let val = if attrs |
| .iter() |
| .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
| { |
| quote_spanned!(attr.name.clone().span()=> { |
| { |
| fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String> |
| where |
| T: ::std::borrow::Borrow<#inner_type> |
| { |
| iterable |
| .into_iter() |
| .map(|val| { |
| clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned() |
| }) |
| } |
| |
| static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<String>> = ::std::sync::OnceLock::new(); |
| static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&str>> = ::std::sync::OnceLock::new(); |
| DEFAULT_VALUES.get_or_init(|| { |
| DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::string::String::as_str).collect() |
| }).iter().copied() |
| } |
| }) |
| } else { |
| quote_spanned!(attr.name.clone().span()=> { |
| { |
| fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String> |
| where |
| T: ::std::borrow::Borrow<#inner_type> |
| { |
| iterable.into_iter().map(|val| val.borrow().to_string()) |
| } |
| |
| static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<String>> = ::std::sync::OnceLock::new(); |
| static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&str>> = ::std::sync::OnceLock::new(); |
| DEFAULT_VALUES.get_or_init(|| { |
| DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::string::String::as_str).collect() |
| }).iter().copied() |
| } |
| }) |
| }; |
| |
| self.methods.push(Method::new( |
| Ident::new("default_values", attr.name.clone().span()), |
| val, |
| )); |
| } |
| |
| Some(MagicAttrName::DefaultValueOsT) => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| let ty = if let Some(ty) = self.ty.as_ref() { |
| ty |
| } else { |
| abort!( |
| attr.name.clone(), |
| "#[arg(default_value_os_t)] (without an argument) can be used \ |
| only on field level\n\n= note: {note}\n\n", |
| |
| note = "see \ |
| https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") |
| }; |
| |
| let val = if let Some(expr) = &attr.value { |
| quote!(#expr) |
| } else { |
| quote!(<#ty as ::std::default::Default>::default()) |
| }; |
| |
| let val = if attrs |
| .iter() |
| .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
| { |
| quote_spanned!(attr.name.clone().span()=> { |
| static DEFAULT_VALUE: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new(); |
| let s = DEFAULT_VALUE.get_or_init(|| { |
| let val: #ty = #val; |
| clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned() |
| }); |
| let s: &'static str = &*s; |
| s |
| }) |
| } else { |
| quote_spanned!(attr.name.clone().span()=> { |
| static DEFAULT_VALUE: ::std::sync::OnceLock<::std::ffi::OsString> = ::std::sync::OnceLock::new(); |
| let s = DEFAULT_VALUE.get_or_init(|| { |
| let val: #ty = #val; |
| ::std::ffi::OsString::from(val) |
| }); |
| let s: &'static ::std::ffi::OsStr = &*s; |
| s |
| }) |
| }; |
| |
| let raw_ident = Ident::new("default_value", attr.name.clone().span()); |
| self.methods.push(Method::new(raw_ident, val)); |
| } |
| |
| Some(MagicAttrName::DefaultValuesOsT) => { |
| assert_attr_kind(attr, &[AttrKind::Arg])?; |
| |
| let ty = if let Some(ty) = self.ty.as_ref() { |
| ty |
| } else { |
| abort!( |
| attr.name.clone(), |
| "#[arg(default_values_os_t)] (without an argument) can be used \ |
| only on field level\n\n= note: {note}\n\n", |
| |
| note = "see \ |
| https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") |
| }; |
| let expr = attr.value_or_abort()?; |
| |
| let container_type = Ty::from_syn_ty(ty); |
| if *container_type != Ty::Vec { |
| abort!( |
| attr.name.clone(), |
| "#[arg(default_values_os_t)] can be used only on Vec types\n\n= note: {note}\n\n", |
| |
| note = "see \ |
| https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") |
| } |
| let inner_type = inner_type(ty); |
| |
| // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and |
| // `Vec<#inner_type>`. |
| let val = if attrs |
| .iter() |
| .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
| { |
| quote_spanned!(attr.name.clone().span()=> { |
| { |
| fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString> |
| where |
| T: ::std::borrow::Borrow<#inner_type> |
| { |
| iterable |
| .into_iter() |
| .map(|val| { |
| clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned().into() |
| }) |
| } |
| |
| static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<::std::ffi::OsString>> = ::std::sync::OnceLock::new(); |
| static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&::std::ffi::OsStr>> = ::std::sync::OnceLock::new(); |
| DEFAULT_VALUES.get_or_init(|| { |
| DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::ffi::OsString::as_os_str).collect() |
| }).iter().copied() |
| } |
| }) |
| } else { |
| quote_spanned!(attr.name.clone().span()=> { |
| { |
| fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString> |
| where |
| T: ::std::borrow::Borrow<#inner_type> |
| { |
| iterable.into_iter().map(|val| val.borrow().into()) |
| } |
| |
| static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<::std::ffi::OsString>> = ::std::sync::OnceLock::new(); |
| static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&::std::ffi::OsStr>> = ::std::sync::OnceLock::new(); |
| DEFAULT_VALUES.get_or_init(|| { |
| DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::ffi::OsString::as_os_str).collect() |
| }).iter().copied() |
| } |
| }) |
| }; |
| |
| self.methods.push(Method::new( |
| Ident::new("default_values", attr.name.clone().span()), |
| val, |
| )); |
| } |
| |
| Some(MagicAttrName::NextDisplayOrder) => { |
| assert_attr_kind(attr, &[AttrKind::Command])?; |
| |
| let expr = attr.value_or_abort()?; |
| self.next_display_order = Some(Method::new(attr.name.clone(), quote!(#expr))); |
| } |
| |
| Some(MagicAttrName::NextHelpHeading) => { |
| assert_attr_kind(attr, &[AttrKind::Command])?; |
| |
| let expr = attr.value_or_abort()?; |
| self.next_help_heading = Some(Method::new(attr.name.clone(), quote!(#expr))); |
| } |
| |
| Some(MagicAttrName::RenameAll) => { |
| let lit = attr.lit_str_or_abort()?; |
| self.casing = CasingStyle::from_lit(lit)?; |
| } |
| |
| Some(MagicAttrName::RenameAllEnv) => { |
| assert_attr_kind(attr, &[AttrKind::Command, AttrKind::Arg])?; |
| |
| let lit = attr.lit_str_or_abort()?; |
| self.env_casing = CasingStyle::from_lit(lit)?; |
| } |
| |
| Some(MagicAttrName::Skip) if actual_attr_kind == AttrKind::Group => { |
| self.skip_group = true; |
| } |
| |
| None |
| // Magic only for the default, otherwise just forward to the builder |
| | Some(MagicAttrName::Short) |
| | Some(MagicAttrName::Long) |
| | Some(MagicAttrName::Env) |
| | Some(MagicAttrName::About) |
| | Some(MagicAttrName::LongAbout) |
| | Some(MagicAttrName::LongHelp) |
| | Some(MagicAttrName::Author) |
| | Some(MagicAttrName::Version) |
| => { |
| let expr = attr.value_or_abort()?; |
| self.push_method(*attr.kind.get(), attr.name.clone(), expr); |
| } |
| |
| // Magic only for the default, otherwise just forward to the builder |
| Some(MagicAttrName::ValueParser) | Some(MagicAttrName::Action) => { |
| let expr = attr.value_or_abort()?; |
| self.push_method(*attr.kind.get(), attr.name.clone(), expr); |
| } |
| |
| // Directives that never receive a value |
| Some(MagicAttrName::ValueEnum) |
| | Some(MagicAttrName::VerbatimDocComment) => { |
| let expr = attr.value_or_abort()?; |
| abort!(expr, "attribute `{}` does not accept a value", attr.name); |
| } |
| |
| // Kinds |
| Some(MagicAttrName::FromGlobal) |
| | Some(MagicAttrName::Subcommand) |
| | Some(MagicAttrName::ExternalSubcommand) |
| | Some(MagicAttrName::Flatten) |
| | Some(MagicAttrName::Skip) => { |
| } |
| } |
| } |
| |
| if self.has_explicit_methods() { |
| if let Kind::Skip(_, attr) = &*self.kind { |
| abort!( |
| self.methods[0].name.span(), |
| "`{}` cannot be used with `#[{}(skip)]", |
| self.methods[0].name, |
| attr.as_str(), |
| ); |
| } |
| if let Kind::FromGlobal(_) = &*self.kind { |
| abort!( |
| self.methods[0].name.span(), |
| "`{}` cannot be used with `#[arg(from_global)]", |
| self.methods[0].name, |
| ); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn push_doc_comment(&mut self, attrs: &[Attribute], short_name: &str, long_name: Option<&str>) { |
| let lines = extract_doc_comment(attrs); |
| |
| if !lines.is_empty() { |
| let (short_help, long_help) = |
| format_doc_comment(&lines, !self.verbatim_doc_comment, self.force_long_help); |
| let short_name = format_ident!("{short_name}"); |
| let short = Method::new( |
| short_name, |
| short_help |
| .map(|h| quote!(#h)) |
| .unwrap_or_else(|| quote!(None)), |
| ); |
| self.doc_comment.push(short); |
| if let Some(long_name) = long_name { |
| let long_name = format_ident!("{long_name}"); |
| let long = Method::new( |
| long_name, |
| long_help |
| .map(|h| quote!(#h)) |
| .unwrap_or_else(|| quote!(None)), |
| ); |
| self.doc_comment.push(long); |
| } |
| } |
| } |
| |
| fn set_kind(&mut self, kind: Sp<Kind>) -> Result<(), syn::Error> { |
| match (self.kind.get(), kind.get()) { |
| (Kind::Arg(_), Kind::FromGlobal(_)) |
| | (Kind::Arg(_), Kind::Subcommand(_)) |
| | (Kind::Arg(_), Kind::Flatten(_)) |
| | (Kind::Arg(_), Kind::Skip(_, _)) |
| | (Kind::Command(_), Kind::Subcommand(_)) |
| | (Kind::Command(_), Kind::Flatten(_)) |
| | (Kind::Command(_), Kind::Skip(_, _)) |
| | (Kind::Command(_), Kind::ExternalSubcommand) |
| | (Kind::Value, Kind::Skip(_, _)) => { |
| self.kind = kind; |
| } |
| |
| (_, _) => { |
| let old = self.kind.name(); |
| let new = kind.name(); |
| abort!(kind.span(), "`{new}` cannot be used with `{old}`"); |
| } |
| } |
| Ok(()) |
| } |
| |
| 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(); |
| quote!( |
| #(#next_display_order)* |
| #(#next_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) -> proc_macro2::TokenStream { |
| let methods = &self.methods; |
| let doc_comment = &self.doc_comment; |
| quote!( #(#doc_comment)* #(#methods)* ) |
| } |
| |
| pub fn group_id(&self) -> TokenStream { |
| self.group_id.clone().raw() |
| } |
| |
| pub fn group_methods(&self) -> TokenStream { |
| let group_methods = &self.group_methods; |
| quote!( #(#group_methods)* ) |
| } |
| |
| pub fn deprecations(&self) -> proc_macro2::TokenStream { |
| let deprecations = &self.deprecations; |
| quote!( #(#deprecations)* ) |
| } |
| |
| 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(); |
| quote!( #(#next_help_heading)* ) |
| } |
| |
| pub fn id(&self) -> TokenStream { |
| self.name.clone().raw() |
| } |
| |
| 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 value_parser(&self, field_type: &Type) -> Method { |
| self.value_parser |
| .clone() |
| .map(|p| { |
| let inner_type = inner_type(field_type); |
| p.resolve(inner_type) |
| }) |
| .unwrap_or_else(|| { |
| let inner_type = inner_type(field_type); |
| if let Some(action) = self.action.as_ref() { |
| let span = action.span(); |
| default_value_parser(inner_type, span) |
| } else { |
| let span = self |
| .action |
| .as_ref() |
| .map(|a| a.span()) |
| .unwrap_or_else(|| self.kind.span()); |
| default_value_parser(inner_type, span) |
| } |
| }) |
| } |
| |
| pub fn action(&self, field_type: &Type) -> Method { |
| self.action |
| .clone() |
| .map(|p| p.resolve(field_type)) |
| .unwrap_or_else(|| { |
| if let Some(value_parser) = self.value_parser.as_ref() { |
| let span = value_parser.span(); |
| default_action(field_type, span) |
| } else { |
| let span = self |
| .value_parser |
| .as_ref() |
| .map(|a| a.span()) |
| .unwrap_or_else(|| self.kind.span()); |
| default_action(field_type, span) |
| } |
| }) |
| } |
| |
| pub fn kind(&self) -> Sp<Kind> { |
| self.kind.clone() |
| } |
| |
| pub fn is_positional(&self) -> bool { |
| self.is_positional |
| } |
| |
| pub fn casing(&self) -> Sp<CasingStyle> { |
| self.casing |
| } |
| |
| pub fn env_casing(&self) -> Sp<CasingStyle> { |
| self.env_casing |
| } |
| |
| pub fn has_explicit_methods(&self) -> bool { |
| self.methods |
| .iter() |
| .any(|m| m.name != "help" && m.name != "long_help") |
| } |
| |
| pub fn skip_group(&self) -> bool { |
| self.skip_group |
| } |
| } |
| |
| #[derive(Clone)] |
| enum ValueParser { |
| Explicit(Method), |
| Implicit(Ident), |
| } |
| |
| impl ValueParser { |
| fn resolve(self, _inner_type: &Type) -> Method { |
| match self { |
| Self::Explicit(method) => method, |
| Self::Implicit(ident) => default_value_parser(_inner_type, ident.span()), |
| } |
| } |
| |
| fn span(&self) -> Span { |
| match self { |
| Self::Explicit(method) => method.name.span(), |
| Self::Implicit(ident) => ident.span(), |
| } |
| } |
| } |
| |
| fn default_value_parser(inner_type: &Type, span: Span) -> Method { |
| let func = Ident::new("value_parser", span); |
| Method::new( |
| func, |
| quote_spanned! { span=> |
| clap::value_parser!(#inner_type) |
| }, |
| ) |
| } |
| |
| #[derive(Clone)] |
| pub enum Action { |
| Explicit(Method), |
| Implicit(Ident), |
| } |
| |
| impl Action { |
| pub fn resolve(self, _field_type: &Type) -> Method { |
| match self { |
| Self::Explicit(method) => method, |
| Self::Implicit(ident) => default_action(_field_type, ident.span()), |
| } |
| } |
| |
| pub fn span(&self) -> Span { |
| match self { |
| Self::Explicit(method) => method.name.span(), |
| Self::Implicit(ident) => ident.span(), |
| } |
| } |
| } |
| |
| fn default_action(field_type: &Type, span: Span) -> Method { |
| let ty = Ty::from_syn_ty(field_type); |
| let args = match *ty { |
| Ty::Vec | Ty::OptionVec | Ty::VecVec | Ty::OptionVecVec => { |
| quote_spanned! { span=> |
| clap::ArgAction::Append |
| } |
| } |
| Ty::Option | Ty::OptionOption => { |
| quote_spanned! { span=> |
| clap::ArgAction::Set |
| } |
| } |
| _ => { |
| if is_simple_ty(field_type, "bool") { |
| quote_spanned! { span=> |
| clap::ArgAction::SetTrue |
| } |
| } else { |
| quote_spanned! { span=> |
| clap::ArgAction::Set |
| } |
| } |
| } |
| }; |
| |
| let func = Ident::new("action", span); |
| Method::new(func, args) |
| } |
| |
| #[allow(clippy::large_enum_variant)] |
| #[derive(Clone)] |
| pub enum Kind { |
| Arg(Sp<Ty>), |
| Command(Sp<Ty>), |
| Value, |
| FromGlobal(Sp<Ty>), |
| Subcommand(Sp<Ty>), |
| Flatten(Sp<Ty>), |
| Skip(Option<AttrValue>, AttrKind), |
| ExternalSubcommand, |
| } |
| |
| impl Kind { |
| pub fn name(&self) -> &'static str { |
| match self { |
| Self::Arg(_) => "arg", |
| Self::Command(_) => "command", |
| Self::Value => "value", |
| Self::FromGlobal(_) => "from_global", |
| Self::Subcommand(_) => "subcommand", |
| Self::Flatten(_) => "flatten", |
| Self::Skip(_, _) => "skip", |
| Self::ExternalSubcommand => "external_subcommand", |
| } |
| } |
| |
| pub fn attr_kind(&self) -> AttrKind { |
| match self { |
| Self::Arg(_) => AttrKind::Arg, |
| Self::Command(_) => AttrKind::Command, |
| Self::Value => AttrKind::Value, |
| Self::FromGlobal(_) => AttrKind::Arg, |
| Self::Subcommand(_) => AttrKind::Command, |
| Self::Flatten(_) => AttrKind::Command, |
| Self::Skip(_, kind) => *kind, |
| Self::ExternalSubcommand => AttrKind::Command, |
| } |
| } |
| |
| pub fn ty(&self) -> Option<&Sp<Ty>> { |
| match self { |
| Self::Arg(ty) |
| | Self::Command(ty) |
| | Self::Flatten(ty) |
| | Self::FromGlobal(ty) |
| | Self::Subcommand(ty) => Some(ty), |
| Self::Value | Self::Skip(_, _) | Self::ExternalSubcommand => None, |
| } |
| } |
| } |
| |
| #[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) -> Result<Option<Self>, syn::Error> { |
| let mut lit = match env::var(env_var) { |
| Ok(val) => { |
| if val.is_empty() { |
| return Ok(None); |
| } |
| LitStr::new(&val, ident.span()) |
| } |
| Err(_) => { |
| abort!( |
| ident, |
| "cannot derive `{}` from Cargo.toml\n\n= note: {note}\n\n= help: {help}\n\n", |
| ident, |
| note = format_args!("`{env_var}` environment variable is not set"), |
| help = format_args!("use `{ident} = \"...\"` to set {ident} manually") |
| ); |
| } |
| }; |
| |
| if ident == "author" { |
| let edited = process_author_str(&lit.value()); |
| lit = LitStr::new(&edited, lit.span()); |
| } |
| |
| Ok(Some(Method::new(ident, quote!(#lit)))) |
| } |
| |
| pub(crate) fn args(&self) -> &TokenStream { |
| &self.args |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct Deprecation { |
| pub span: Span, |
| pub id: &'static str, |
| pub version: &'static str, |
| pub description: String, |
| } |
| |
| impl Deprecation { |
| fn attribute(version: &'static str, old: AttrKind, new: AttrKind, span: Span) -> Self { |
| Self { |
| span, |
| id: "old_attribute", |
| version, |
| description: format!( |
| "Attribute `#[{}(...)]` has been deprecated in favor of `#[{}(...)]`", |
| old.as_str(), |
| new.as_str() |
| ), |
| } |
| } |
| } |
| |
| impl ToTokens for Deprecation { |
| fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) { |
| let tokens = if cfg!(feature = "deprecated") { |
| let Deprecation { |
| span, |
| id, |
| version, |
| description, |
| } = self; |
| let span = *span; |
| let id = Ident::new(id, span); |
| |
| quote_spanned!(span=> { |
| #[deprecated(since = #version, note = #description)] |
| fn #id() {} |
| #id(); |
| }) |
| } else { |
| quote!() |
| }; |
| |
| tokens.to_tokens(ts); |
| } |
| } |
| |
| fn assert_attr_kind(attr: &ClapAttr, possible_kind: &[AttrKind]) -> Result<(), syn::Error> { |
| if *attr.kind.get() == AttrKind::Clap || *attr.kind.get() == AttrKind::StructOpt { |
| // deprecated |
| } else if !possible_kind.contains(attr.kind.get()) { |
| let options = possible_kind |
| .iter() |
| .map(|k| format!("`#[{}({})]`", k.as_str(), attr.name)) |
| .collect::<Vec<_>>(); |
| abort!( |
| attr.name, |
| "Unknown `#[{}({})]` attribute ({} exists)", |
| attr.kind.as_str(), |
| attr.name, |
| options.join(", ") |
| ); |
| } |
| Ok(()) |
| } |
| |
| /// 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 |
| } |
| |
| /// Defines the casing for the attributes long representation. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| 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) -> Result<Sp<Self>, syn::Error> { |
| use self::CasingStyle::*; |
| |
| let normalized = name.value().to_upper_camel_case().to_lowercase(); |
| let cs = |kind| Sp::new(kind, name.span()); |
| |
| let s = 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}`"), |
| }; |
| Ok(s) |
| } |
| } |
| |
| #[derive(Clone)] |
| pub enum Name { |
| Derived(Ident), |
| Assigned(TokenStream), |
| } |
| |
| impl Name { |
| 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) |
| } |
| } |
| } |
| } |