use crate::*;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use syn::{Field, Lit, LitBool, LitStr, Meta, NestedMeta, Path};
pub(crate) struct FieldFormatting {
pub chip: Option<LitStr>,
pub prefix: Option<LitStr>,
pub new_line: Option<LitBool>,
pub default: Option<LitBool>,
pub custom_fmt: bool,
pub some_omitted: bool,
}
impl Default for FieldFormatting {
fn default() -> Self {
Self {
chip: None,
prefix: None,
new_line: None,
default: None,
custom_fmt: false,
some_omitted: true,
}
}
}
macro_rules! map_to_ts {
($self:expr, $($id:ident)*) => {
$(let $id = $self.$id.as_ref().map(|lit| quote!($id: #lit,));)*
}
}
impl ToTokens for FieldFormatting {
fn to_tokens(&self, tokens: &mut TokenStream) {
if !self.custom_fmt {
return;
}
map_to_ts!(self, chip prefix new_line default);
let call_default = if self.some_omitted {
quote!(..Default::default())
} else {
TokenStream::new()
};
tokens.extend(quote! {
.fmt(::ezmenu::StructFieldFormatting {
#chip
#prefix
#new_line
#default
#call_default
})
});
}
}
struct DefaultValue(Lit);
impl ToTokens for DefaultValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
let default = &self.0;
tokens.extend(if let Lit::Str(_) = default {
quote! {
.default(#default)
}
} else {
quote! {
.default(stringify!(#default))
}
});
}
}
pub(crate) enum FieldMenuInitKind {
Simple(Ident),
Mapped(Ident, Path),
}
impl ToTokens for FieldMenuInitKind {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
Self::Simple(ident) => quote! {
#ident: menu.next()?,
},
Self::Mapped(ident, func) => quote! {
#ident: menu.next_map(#func)?,
},
})
}
}
#[derive(Default)]
struct MetaFieldDesc {
msg: Option<LitStr>,
default: Option<Lit>,
then: Option<Path>,
fmt: FieldFormatting,
}
fn parse_arg_nested(
MetaFieldDesc {
ref mut msg,
ref mut default,
ref mut then,
fmt:
FieldFormatting {
ref mut chip,
ref mut prefix,
ref mut new_line,
default: ref mut disp_default,
..
},
}: &mut MetaFieldDesc,
arg: String,
nested: &NestedMeta,
span: NestedMeta,
) {
match arg.as_str() {
s @ "msg" => run_nested_str(s, nested, msg),
s @ "default" => run_nested(s, nested, default),
s @ "then" => run_nested_path(s, nested, then),
s @ "chip" => run_nested_str(s, nested, chip),
s @ "prefix" => run_nested_str(s, nested, prefix),
s @ "new_line" => run_nested_bool(s, nested, new_line),
s @ "display_default" => run_nested_bool(s, nested, disp_default),
s => abort_invalid_arg_name(span, s),
}
}
fn parse_arg_nv(
MetaFieldDesc {
ref mut msg,
ref mut default,
fmt:
FieldFormatting {
ref mut chip,
ref mut prefix,
ref mut new_line,
default: ref mut disp_default,
..
},
..
}: &mut MetaFieldDesc,
arg: String,
lit: Lit,
span: NestedMeta,
) {
match arg.as_str() {
s @ "msg" => run_nv_str(s, lit, msg),
"default" => *default = Some(lit),
s @ "chip" => run_nv_str(s, lit, chip),
s @ "prefix" => run_nv_str(s, lit, prefix),
s @ "new_line" => run_nv_bool(s, lit, new_line),
s @ "display_default" => run_nv_bool(s, lit, disp_default),
s => abort_invalid_arg_name(span, s),
}
}
impl From<Meta> for MetaFieldDesc {
fn from(meta: Meta) -> Self {
let mut desc = MetaFieldDesc::default();
parse(&mut desc, parse_arg_nested, parse_arg_nv, meta);
let MetaFieldDesc {
msg,
default,
then,
fmt:
FieldFormatting {
chip,
prefix,
new_line,
default: disp_default,
..
},
} = desc;
let custom_fmt =
chip.is_some() || prefix.is_some() || new_line.is_some() || disp_default.is_some();
let some_omitted =
!(chip.is_some() && prefix.is_some() && new_line.is_some() && disp_default.is_some());
Self {
msg,
default,
then,
fmt: FieldFormatting {
chip,
prefix,
new_line,
default: disp_default,
custom_fmt,
some_omitted,
},
}
}
}
pub(crate) struct FieldMenuInit {
msg: LitStr,
default: Option<DefaultValue>,
fmt: FieldFormatting,
pub kind: FieldMenuInitKind,
}
impl From<Field> for FieldMenuInit {
fn from(field: Field) -> Self {
let ident = field.ident.unwrap();
let desc = match get_meta_attr(field.attrs) {
Some(meta) => MetaFieldDesc::from(meta),
_ => Default::default(),
};
let msg = desc
.msg
.unwrap_or_else(|| LitStr::new(format!("{}", ident).as_str(), ident.span()));
let default = desc.default.map(DefaultValue);
let kind = match desc.then {
Some(func) => FieldMenuInitKind::Mapped(ident, func),
_ => FieldMenuInitKind::Simple(ident),
};
Self {
msg,
default,
fmt: desc.fmt,
kind,
}
}
}
impl ToTokens for FieldMenuInit {
fn to_tokens(&self, tokens: &mut TokenStream) {
{
let msg = &self.msg;
tokens.extend(quote! {
::ezmenu::StructField::from(#msg)
});
}
self.default.to_tokens(tokens);
self.fmt.to_tokens(tokens);
}
}