use super::StructItem;
use crate::util::type_is_bool;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
Error, Expr, Field, Ident, Lifetime, LitStr, Meta, Path, Token, Type, punctuated::Punctuated,
};
mod flag_item;
mod flatten_item;
mod parameter_item;
mod repeat_item;
mod subcommands_item;
use flag_item::FlagItem;
use flatten_item::FlattenItem;
use parameter_item::ParameterItem;
use repeat_item::RepeatItem;
use subcommands_item::SubcommandsItem;
pub enum ValueParserExpr {
Str(Expr),
OsStr(Expr),
}
pub enum ExprRequest {
Str,
OsStr,
}
pub struct SerdeStrategy {
pub state_machine_type: Option<Type>,
pub state_machine_init: Option<TokenStream>,
pub match_expr: TokenStream,
pub serde_keys: SerdeKeys,
}
impl SerdeStrategy {
pub fn skip() -> Self {
Self {
state_machine_type: None,
state_machine_init: None,
match_expr: quote! {},
serde_keys: SerdeKeys::default(),
}
}
}
#[derive(Clone)]
pub enum SerdeKeys {
Lit(Vec<LitStr>),
Expr(TokenStream),
}
impl Default for SerdeKeys {
fn default() -> Self {
Self::Lit(Default::default())
}
}
impl SerdeKeys {
pub fn gen_match_pattern(&self) -> Option<TokenStream> {
match self {
Self::Lit(v) => {
if v.is_empty() {
None
} else {
Some(quote! { #(#v)|* })
}
}
Self::Expr(e) => Some(quote! { #e }),
}
}
pub fn help_key(&self) -> Option<&LitStr> {
match self {
Self::Lit(v) => v.first(),
Self::Expr(_) => None,
}
}
pub fn all_literal_keys<'a>(
mut iter: impl Iterator<Item = &'a SerdeKeys>,
) -> Option<Vec<&'a LitStr>> {
iter.try_fold(vec![], |mut vec, serde_keys| -> Option<Vec<&LitStr>> {
match serde_keys {
SerdeKeys::Lit(v) => {
vec.extend(v);
}
SerdeKeys::Expr(_) => {
return None;
}
}
Some(vec)
})
}
}
#[allow(clippy::large_enum_variant)]
pub enum FieldItem {
Flag(FlagItem),
Parameter(ParameterItem),
Repeat(RepeatItem),
Flatten(FlattenItem),
Subcommands(SubcommandsItem),
}
impl FieldItem {
pub fn new(field: &Field, struct_item: &StructItem) -> Result<Self, Error> {
for attr in &field.attrs {
if attr.path().is_ident("conf") || attr.path().is_ident("arg") {
let nested =
attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
if let Some(meta) = nested.first() {
let path = meta.path();
if path.is_ident("flag") {
return Ok(Self::Flag(FlagItem::new(field, struct_item)?));
} else if path.is_ident("parameter") {
return Ok(Self::Parameter(ParameterItem::new(field, struct_item)?));
} else if path.is_ident("repeat") {
return Ok(Self::Repeat(RepeatItem::new(field, struct_item)?));
} else if path.is_ident("flatten") {
return Ok(Self::Flatten(FlattenItem::new(field, struct_item)?));
} else if path.is_ident("subcommands") {
return Ok(Self::Subcommands(SubcommandsItem::new(field, struct_item)?));
}
}
}
}
Ok(if type_is_bool(&field.ty) {
Self::Flag(FlagItem::new(field, struct_item)?)
} else {
Self::Parameter(ParameterItem::new(field, struct_item)?)
})
}
pub fn is_single_option(&self) -> bool {
matches!(
self,
Self::Flag(..) | Self::Parameter(..) | Self::Repeat(..)
)
}
pub fn get_field_name(&self) -> &Ident {
match self {
Self::Flag(item) => item.get_field_name(),
Self::Parameter(item) => item.get_field_name(),
Self::Repeat(item) => item.get_field_name(),
Self::Flatten(item) => item.get_field_name(),
Self::Subcommands(item) => item.get_field_name(),
}
}
pub fn get_field_type(&self) -> Type {
match self {
Self::Flag(item) => item.get_field_type(),
Self::Parameter(item) => item.get_field_type(),
Self::Repeat(item) => item.get_field_type(),
Self::Flatten(item) => item.get_field_type(),
Self::Subcommands(item) => item.get_field_type(),
}
}
pub fn gen_program_option_node(&self) -> Result<Option<TokenStream>, Error> {
match self {
Self::Flag(item) => item.gen_program_option_node(),
Self::Parameter(item) => item.gen_program_option_node(),
Self::Repeat(item) => item.gen_program_option_node(),
Self::Flatten(item) => item.gen_program_option_node(),
Self::Subcommands(item) => item.gen_program_option_node(),
}
}
pub fn gen_push_subcommands(
&self,
subcommands_ident: &Ident,
parsed_env: &Ident,
) -> Result<TokenStream, Error> {
match self {
Self::Flag(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
Self::Parameter(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
Self::Repeat(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
Self::Flatten(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
Self::Subcommands(item) => item.gen_push_subcommands(subcommands_ident, parsed_env),
}
}
fn gen_initializer(&self, conf_context_ident: &Ident) -> Result<(TokenStream, bool), Error> {
match self {
Self::Flag(item) => item.gen_initializer(conf_context_ident),
Self::Parameter(item) => item.gen_initializer(conf_context_ident),
Self::Repeat(item) => item.gen_initializer(conf_context_ident),
Self::Flatten(item) => item.gen_initializer(conf_context_ident),
Self::Subcommands(item) => item.gen_initializer(conf_context_ident),
}
}
fn gen_initializer_with_doc_val(
&self,
conf_context_ident: &Ident,
doc_name_ident: &Ident,
doc_val_ident: &Ident,
) -> Result<(TokenStream, bool), Error> {
match self {
Self::Flag(item) => {
item.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)
}
Self::Parameter(item) => {
item.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)
}
Self::Repeat(item) => {
item.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)
}
Self::Flatten(_item) => unimplemented!("uses a custom match arm"),
Self::Subcommands(_item) => unimplemented!("would have to use a custom match arm"),
}
}
pub fn gen_initialize_from_conf_context_and_push_errors(
&self,
conf_context_ident: &Ident,
errors_ident: &Ident,
) -> Result<TokenStream, Error> {
let field_name = self.get_field_name();
let field_type = self.get_field_type();
let (initializer, returns_multiple_errors) = self.gen_initializer(conf_context_ident)?;
let (error_type, extend_fn) = if returns_multiple_errors {
(
quote! { ::std::vec::Vec<::conf::InnerError> },
quote! { extend },
)
} else {
(quote! { ::conf::InnerError }, quote! { push })
};
Ok(quote! {
{
fn #field_name(
#conf_context_ident: &::conf::ConfContext<'_>
) -> Result<#field_type, #error_type> {
#initializer
}
match #field_name(#conf_context_ident) {
Ok(val) => Some(val),
Err(errs) => {
#errors_ident.#extend_fn(errs);
None
}
}
}
})
}
pub fn gen_initialize_from_conf_context_and_doc_val_and_push_errors(
&self,
conf_context_ident: &Ident,
doc_name_ident: &Ident,
doc_val_ident: &Ident,
errors_ident: &Ident,
) -> Result<TokenStream, Error> {
let field_name = self.get_field_name();
let field_type = self.get_field_type();
let serde_type = self.get_serde_type();
let (initializer, returns_multiple_errors) =
self.gen_initializer_with_doc_val(conf_context_ident, doc_name_ident, doc_val_ident)?;
let (error_type, extend_fn) = if returns_multiple_errors {
(
quote! { ::std::vec::Vec<::conf::InnerError> },
quote! { extend },
)
} else {
(quote! { ::conf::InnerError }, quote! { push })
};
Ok(quote! {
{
fn #field_name(
#conf_context_ident: &::conf::ConfContext<'_>,
#doc_name_ident: &str,
#doc_val_ident: #serde_type
) -> Result<#field_type, #error_type> {
#initializer
}
match #field_name(&#conf_context_ident, #doc_name_ident, #doc_val_ident) {
Ok(val) => Some(val),
Err(errs) => {
#errors_ident.#extend_fn(errs);
None
}
}
}
})
}
pub fn gen_serde_strategy(
&self,
ct: &Lifetime,
ctxt: &Ident,
nvp: &Ident,
nvp_type: &Ident,
errors_ident: &Ident,
) -> Result<SerdeStrategy, Error> {
if self.get_serde_skip() {
return Ok(SerdeStrategy::skip());
}
match self {
Self::Flag(_) | Self::Parameter(_) | Self::Repeat(_) => {
self.gen_simple_serde_strategy(ct, ctxt, nvp, nvp_type, errors_ident)
}
Self::Flatten(item) => item.gen_serde_strategy(ct, ctxt, nvp, nvp_type, errors_ident),
Self::Subcommands(item) => {
item.gen_serde_strategy(ct, ctxt, nvp, nvp_type, errors_ident)
}
}
}
fn gen_simple_serde_strategy(
&self,
_ct: &Lifetime,
ctxt: &Ident,
nvp: &Ident,
nvp_type: &Ident,
errors_ident: &Ident,
) -> Result<SerdeStrategy, Error> {
let field_name = self.get_field_name();
let field_name_str = field_name.to_string();
let serde_name_str = self.get_serde_name();
let serde_aliases = self.get_serde_aliases();
let serde_type = self.get_serde_type();
let deserialize_with = self.get_serde_deserialize_with();
let conf_context_ident = Ident::new("__conf_context__", Span::call_site());
let doc_name_ident = Ident::new("__doc_name__", Span::call_site());
let doc_val_ident = Ident::new("__doc_val__", Span::call_site());
let initializer = self.gen_initialize_from_conf_context_and_doc_val_and_push_errors(
&conf_context_ident,
&doc_name_ident,
&doc_val_ident,
errors_ident,
)?;
let deserialize_call = if let Some(deserialize_with_path) = deserialize_with {
quote! {
{
struct __DeserializeWith;
impl<'dedede2> ::serde::de::DeserializeSeed<'dedede2> for __DeserializeWith {
type Value = #serde_type;
fn deserialize<__D>(self, __deserializer: __D) -> ::std::result::Result<Self::Value, __D::Error>
where
__D: ::serde::de::Deserializer<'dedede2>
{
(#deserialize_with_path)(__deserializer)
}
}
#nvp.next_value_seed(__DeserializeWith)
}
}
} else {
quote! {
#nvp.next_value::<#serde_type>()
}
};
let match_expr = quote! {
{
if #field_name.is_some() {
#errors_ident.push(
InnerError::serde(
#ctxt.document_name,
#field_name_str,
#nvp_type::Error::duplicate_field(#serde_name_str)
)
);
} else {
#field_name = Some(match #deserialize_call {
Ok(#doc_val_ident) => {
let #conf_context_ident: &::conf::ConfContext = &#ctxt.conf_context;
let #doc_name_ident: &str = #ctxt.document_name;
#initializer
}
Err(__err__) => {
#errors_ident.push(
InnerError::serde(
#ctxt.document_name,
#field_name_str,
__err__
)
);
None
}
});
}
},
};
let mut all_names = vec![serde_name_str];
all_names.extend(serde_aliases);
Ok(SerdeStrategy {
state_machine_type: None,
state_machine_init: None,
match_expr,
serde_keys: SerdeKeys::Lit(all_names),
})
}
fn get_serde_name(&self) -> LitStr {
match self {
Self::Flag(item) => item.get_serde_name(),
Self::Parameter(item) => item.get_serde_name(),
Self::Repeat(item) => item.get_serde_name(),
Self::Flatten(_item) => unimplemented!(),
Self::Subcommands(_item) => unimplemented!(),
}
}
fn get_serde_aliases(&self) -> Vec<LitStr> {
match self {
Self::Flag(item) => item.get_serde_aliases(),
Self::Parameter(item) => item.get_serde_aliases(),
Self::Repeat(item) => item.get_serde_aliases(),
Self::Flatten(_item) => unimplemented!(),
Self::Subcommands(_item) => unimplemented!(),
}
}
fn get_serde_deserialize_with(&self) -> Option<Path> {
match self {
Self::Flag(item) => item.get_serde_deserialize_with(),
Self::Parameter(item) => item.get_serde_deserialize_with(),
Self::Repeat(item) => item.get_serde_deserialize_with(),
Self::Flatten(_item) => unimplemented!(),
Self::Subcommands(_item) => unimplemented!(),
}
}
fn get_serde_type(&self) -> Type {
match self {
Self::Flag(item) => item.get_serde_type(),
Self::Parameter(item) => item.get_serde_type(),
Self::Repeat(item) => item.get_serde_type(),
Self::Flatten(_item) => unimplemented!(),
Self::Subcommands(_item) => unimplemented!(),
}
}
fn get_serde_skip(&self) -> bool {
match self {
Self::Flag(item) => item.get_serde_skip(),
Self::Parameter(item) => item.get_serde_skip(),
Self::Repeat(item) => item.get_serde_skip(),
Self::Flatten(item) => item.get_serde_skip(),
Self::Subcommands(item) => item.get_serde_skip(),
}
}
pub fn gen_debug_asserts(&self, struct_ident: &Ident) -> Result<TokenStream, Error> {
match self {
Self::Flag(item) => item.gen_debug_asserts(struct_ident),
Self::Parameter(item) => item.gen_debug_asserts(struct_ident),
Self::Repeat(item) => item.gen_debug_asserts(struct_ident),
Self::Flatten(item) => item.gen_debug_asserts(struct_ident),
Self::Subcommands(item) => item.gen_debug_asserts(struct_ident),
}
}
}