use super::StructItem;
use crate::util::*;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
Error, Field, Ident, LitChar, LitStr, Path, Type, meta::ParseNestedMeta, parse_quote,
spanned::Spanned, token,
};
pub struct FlagSerdeItem {
pub rename: Option<LitStr>,
pub aliases: Vec<LitStr>,
pub skip: bool,
pub deserialize_with: Option<Path>,
pub try_from: Option<Type>,
span: Span,
}
impl FlagSerdeItem {
pub fn new(meta: ParseNestedMeta<'_>) -> Result<Self, Error> {
let mut result = Self {
rename: None,
aliases: Vec::new(),
skip: false,
deserialize_with: None,
try_from: None,
span: meta.input.span(),
};
if meta.input.peek(token::Paren) {
meta.parse_nested_meta(|meta| {
let path = meta.path.clone();
if path.is_ident("rename") {
set_once(
&path,
&mut result.rename,
Some(parse_required_value::<LitStr>(meta)?),
)
} else if path.is_ident("alias") {
result.aliases.push(parse_required_value::<LitStr>(meta)?);
Ok(())
} else if path.is_ident("skip") {
result.skip = true;
Ok(())
} else if path.is_ident("deserialize_with") {
set_once(
&path,
&mut result.deserialize_with,
Some(parse_path_from_str(meta)?),
)
} else if path.is_ident("try_from") {
set_once(
&path,
&mut result.try_from,
Some(parse_type_from_str(meta)?),
)
} else {
Err(meta.error("unrecognized conf(serde) option"))
}
})?;
}
if let (Some(try_from), Some(deserialize_with)) =
(&result.try_from, &result.deserialize_with)
{
return Err(mutually_exclusive_error(
"try_from",
try_from,
"deserialize_with",
deserialize_with,
));
}
Ok(result)
}
}
impl GetSpan for FlagSerdeItem {
fn get_span(&self) -> Span {
self.span
}
}
pub struct FlagItem {
field_name: Ident,
short_switch: Option<LitChar>,
long_switch: Option<LitStr>,
aliases: Option<LitStrArray>,
env_name: Option<LitStr>,
env_aliases: Option<LitStrArray>,
serde: Option<FlagSerdeItem>,
doc_string: Option<String>,
}
impl FlagItem {
pub fn new(field: &Field, _struct_item: &StructItem) -> Result<Self, Error> {
let field_name = field
.ident
.clone()
.ok_or_else(|| Error::new(field.span(), "missing identifier"))?;
let mut result = Self {
field_name,
short_switch: None,
long_switch: None,
aliases: None,
env_name: None,
env_aliases: None,
serde: None,
doc_string: None,
};
for attr in &field.attrs {
maybe_append_doc_string(&mut result.doc_string, &attr.meta)?;
if attr.path().is_ident("conf") || attr.path().is_ident("arg") {
attr.parse_nested_meta(|meta| {
let path = meta.path.clone();
if path.is_ident("flag") {
Ok(())
} else if path.is_ident("short") {
set_once(
&path,
&mut result.short_switch,
parse_optional_value::<LitChar>(meta)?
.or(make_short(&result.field_name, path.span())),
)
} else if path.is_ident("long") {
set_once(
&path,
&mut result.long_switch,
parse_optional_value::<LitStr>(meta)?
.or(make_long(&result.field_name, path.span())),
)
} else if path.is_ident("aliases") {
set_once(
&path,
&mut result.aliases,
Some(parse_required_value::<LitStrArray>(meta)?),
)
} else if path.is_ident("env") {
set_once(
&path,
&mut result.env_name,
parse_optional_value::<LitStr>(meta)?
.or(make_env(&result.field_name, path.span())),
)
} else if path.is_ident("env_aliases") {
set_once(
&path,
&mut result.env_aliases,
Some(parse_required_value::<LitStrArray>(meta)?),
)
} else if path.is_ident("serde") {
set_once(&path, &mut result.serde, Some(FlagSerdeItem::new(meta)?))
} else {
Err(meta.error("unrecognized conf flag option"))
}
})?;
}
}
if let Some(aliases) = &result.aliases {
if result.long_switch.is_none() && !aliases.is_empty() {
return Err(Error::new(
aliases.get_span(),
"Setting aliases without setting a long-switch is an error, \
make one of the aliases the primary switch name.",
));
}
}
if let Some(env_aliases) = &result.env_aliases {
if result.env_name.is_none() && !env_aliases.is_empty() {
return Err(Error::new(
env_aliases.get_span(),
"Setting env_aliases without setting an env is an error, \
make one of the aliases the primary env.",
));
}
}
Ok(result)
}
pub fn get_field_name(&self) -> &Ident {
&self.field_name
}
pub fn get_field_type(&self) -> Type {
parse_quote! { bool }
}
pub fn get_serde_name(&self) -> LitStr {
self.serde
.as_ref()
.and_then(|serde| serde.rename.clone())
.unwrap_or_else(|| LitStr::new(&self.field_name.to_string(), self.field_name.span()))
}
pub fn get_serde_aliases(&self) -> Vec<LitStr> {
self.serde
.as_ref()
.map(|serde| serde.aliases.clone())
.unwrap_or_default()
}
pub fn get_serde_type(&self) -> Type {
if let Some(try_from_type) = self.serde.as_ref().and_then(|serde| serde.try_from.clone()) {
return try_from_type;
}
parse_quote! { bool }
}
pub fn get_serde_try_from(&self) -> Option<Type> {
self.serde.as_ref().and_then(|serde| serde.try_from.clone())
}
pub fn get_serde_skip(&self) -> bool {
self.serde.as_ref().map(|serde| serde.skip).unwrap_or(false)
}
pub fn get_serde_deserialize_with(&self) -> Option<Path> {
self.serde
.as_ref()
.and_then(|serde| serde.deserialize_with.clone())
}
pub fn has_serde_source(&self) -> bool {
self.serde.is_some() && !self.get_serde_skip()
}
pub fn has_cli_or_env_source(&self) -> bool {
self.short_switch.is_some() || self.long_switch.is_some() || self.env_name.is_some()
}
pub fn gen_program_option_node(&self) -> Result<Option<TokenStream>, Error> {
let id = self.field_name.to_string();
let description = quote_opt_cow(&self.doc_string);
let short_form = quote_opt(&self.short_switch);
let long_form = quote_opt_cow(&self.long_switch);
let env_form = quote_opt_cow(&self.env_name);
let has_serde_source = self.has_serde_source();
let aliases = self
.aliases
.as_ref()
.map(|x| x.quote_elements_cow())
.unwrap_or_default();
let env_aliases = self
.env_aliases
.as_ref()
.map(|x| x.quote_elements_cow())
.unwrap_or_default();
Ok(Some(quote! {
::conf::lazybuf::Node::Leaf(::conf::ProgramOption {
id: ::std::borrow::Cow::Borrowed(#id),
parse_type: ::conf::ParseType::Flag,
description: #description,
short_form: #short_form,
long_form: #long_form,
aliases: ::std::borrow::Cow::Borrowed(&[#aliases]),
env_form: #env_form,
env_aliases: ::std::borrow::Cow::Borrowed(&[#env_aliases]),
default_help_str: None,
is_required: false,
allow_hyphen_values: false,
allow_negative_numbers: false,
default_if_missing: None,
secret: Some(false),
is_positional: false,
has_serde_source: #has_serde_source,
})
}))
}
pub fn gen_push_subcommands(
&self,
_subcommands_ident: &Ident,
_parsed_env: &Ident,
) -> Result<TokenStream, syn::Error> {
Ok(quote! {})
}
pub fn gen_initializer(
&self,
conf_context_ident: &Ident,
) -> Result<(TokenStream, bool), Error> {
let id = self.field_name.to_string();
if !self.has_cli_or_env_source() {
return Ok((
quote! {
let _ = #conf_context_ident;
Ok(false)
},
false,
));
}
Ok((
quote! {
let (src, val) = #conf_context_ident.get_boolean_opt(#id)?;
#conf_context_ident.log_config_event(#id, src);
Ok(val)
},
false,
))
}
pub fn gen_initializer_with_doc_val(
&self,
conf_context_ident: &Ident,
doc_name: &Ident,
doc_val: &Ident,
) -> Result<(TokenStream, bool), Error> {
let id = self.field_name.to_string();
let field_name_str = self.field_name.to_string();
let try_from = self.get_serde_try_from();
if !self.has_cli_or_env_source() {
if let Some(_try_from_type) = try_from {
return Ok((
quote! {
#conf_context_ident.log_config_event(
#id,
::conf::ConfValueSource::Document(#doc_name)
);
<bool as ::core::convert::TryFrom<_>>::try_from(#doc_val)
.map_err(|err| ::conf::InnerError::serde(
#doc_name,
#field_name_str,
err
))
},
false,
));
} else {
return Ok((
quote! {
#conf_context_ident.log_config_event(
#id,
::conf::ConfValueSource::Document(#doc_name)
);
Ok(#doc_val)
},
false,
));
}
}
if let Some(_try_from_type) = try_from {
Ok((
quote! {
let (src, val) = #conf_context_ident.get_boolean_opt(#id)?;
#conf_context_ident.log_config_event(#id, src);
if src.is_default() {
<bool as ::core::convert::TryFrom<_>>::try_from(#doc_val)
.map_err(|err| ::conf::InnerError::serde(
#doc_name,
#field_name_str,
err
))
} else {
Ok(val)
}
},
false,
))
} else {
Ok((
quote! {
let (src, val) = #conf_context_ident.get_boolean_opt(#id)?;
#conf_context_ident.log_config_event(#id, src);
if src.is_default() {
Ok(#doc_val)
} else {
Ok(val)
}
},
false,
))
}
}
pub fn gen_debug_asserts(&self, _struct_ident: &Ident) -> Result<TokenStream, Error> {
Ok(quote! {})
}
}