// 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};
impl Item { pub(crate) 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);
letmut 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(crate) 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);
letmut 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(crate) 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);
letmut 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(crate) 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); letmut 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"
);
}
}
fn push_method_(&mutself, 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);
}
}
} elseif 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);
}
}
} elseif name == "value_parser" { self.value_parser = Some(ValueParser::Explicit(Method::new(name, arg)));
} elseif 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(&mutself, attrs: &[ClapAttr]) -> Result<(), syn::Error> { for attr in attrs { iflet 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.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.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.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.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.span());
Some(kind)
}
_ => None,
};
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])?;
let ty = iflet 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 = iflet Some(expr) = &attr.value {
quote!(#expr)
} else {
quote!(<#tyas ::std::default::Default>::default())
};
let val = if attrs
.iter()
.any(|a| a.magic == Some(MagicAttrName::ValueEnum))
{
quote_spanned!(attr.name.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.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.span()); self.methods.push(Method::new(raw_ident, val));
}
let ty = iflet 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.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()
})
}
let ty = iflet 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 = iflet Some(expr) = &attr.value {
quote!(#expr)
} else {
quote!(<#tyas ::std::default::Default>::default())
};
let val = if attrs
.iter()
.any(|a| a.magic == Some(MagicAttrName::ValueEnum))
{
quote_spanned!(attr.name.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.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.span()); self.methods.push(Method::new(raw_ident, val));
}
let ty = iflet 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.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()
})
}
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);
}
/// generate methods from attributes on top of struct or enum pub(crate) 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(crate) 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(crate) fn field_methods(&self) -> TokenStream { let methods = &self.methods; let doc_comment = &self.doc_comment;
quote!( #(#doc_comment)* #(#methods)* )
}
/// Defines the casing for the attributes long representation. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) 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,
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.