aboutsummaryrefslogtreecommitdiff
path: root/vendor/clap_derive/src/item.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/clap_derive/src/item.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/clap_derive/src/item.rs')
-rw-r--r--vendor/clap_derive/src/item.rs1468
1 files changed, 1468 insertions, 0 deletions
diff --git a/vendor/clap_derive/src/item.rs b/vendor/clap_derive/src/item.rs
new file mode 100644
index 0000000..114849f
--- /dev/null
+++ b/vendor/clap_derive/src/item.rs
@@ -0,0 +1,1468 @@
+// 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 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)
+ }
+ }
+ }
+}