use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Data, DeriveInput, Fields, Ident, LitStr, Token};
use crate::shared::named_field_ident;
#[derive(Default)]
pub(crate) struct TreeAttrs {
style: Option<LitStr>,
guide_style: Option<LitStr>,
}
pub(crate) struct TreeAttr {
pub(crate) key: Ident,
pub(crate) value: TreeAttrValue,
}
pub(crate) enum TreeAttrValue {
Str(LitStr),
Flag,
}
impl Parse for TreeAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let key: Ident = input.parse()?;
if input.peek(Token![=]) {
let _eq: Token![=] = input.parse()?;
if input.peek(LitStr) {
let lit: LitStr = input.parse()?;
Ok(TreeAttr {
key,
value: TreeAttrValue::Str(lit),
})
} else {
Err(input.error("expected string literal"))
}
} else {
Ok(TreeAttr {
key,
value: TreeAttrValue::Flag,
})
}
}
}
pub(crate) fn parse_tree_attrs(input: &DeriveInput) -> syn::Result<TreeAttrs> {
let mut attrs = TreeAttrs::default();
for attr in &input.attrs {
if !attr.path().is_ident("tree") {
continue;
}
let items: Punctuated<TreeAttr, Token![,]> =
attr.parse_args_with(Punctuated::parse_terminated)?;
for item in items {
let key_str = item.key.to_string();
match key_str.as_str() {
"style" => {
attrs.style = Some(tree_expect_str(&item, "style")?);
}
"guide_style" => {
attrs.guide_style = Some(tree_expect_str(&item, "guide_style")?);
}
_ => {
return Err(syn::Error::new_spanned(
&item.key,
format!("unknown tree attribute `{}`", key_str),
));
}
}
}
}
Ok(attrs)
}
pub(crate) fn tree_expect_str(attr: &TreeAttr, name: &str) -> syn::Result<LitStr> {
match &attr.value {
TreeAttrValue::Str(s) => Ok(s.clone()),
_ => Err(syn::Error::new_spanned(
&attr.key,
format!("`{}` expects a string literal", name),
)),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TreeFieldKind {
Label,
Children,
Leaf,
None,
}
pub(crate) fn parse_tree_field_attrs(field: &syn::Field) -> syn::Result<TreeFieldKind> {
let mut kind = TreeFieldKind::None;
for attr in &field.attrs {
if !attr.path().is_ident("tree") {
continue;
}
let ident: Ident = attr.parse_args()?;
let ident_str = ident.to_string();
match ident_str.as_str() {
"label" => {
if kind != TreeFieldKind::None {
return Err(syn::Error::new_spanned(
&ident,
"field already has a tree role assigned",
));
}
kind = TreeFieldKind::Label;
}
"children" => {
if kind != TreeFieldKind::None {
return Err(syn::Error::new_spanned(
&ident,
"field already has a tree role assigned",
));
}
kind = TreeFieldKind::Children;
}
"leaf" => {
if kind != TreeFieldKind::None {
return Err(syn::Error::new_spanned(
&ident,
"field already has a tree role assigned",
));
}
kind = TreeFieldKind::Leaf;
}
_ => {
return Err(syn::Error::new_spanned(
&ident,
format!(
"unknown tree field attribute `{}`. Expected one of: label, children, leaf",
ident_str
),
));
}
}
}
Ok(kind)
}
pub(crate) fn derive_tree_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_name = &input.ident;
let fields = match &input.data {
Data::Struct(data_struct) => match &data_struct.fields {
Fields::Named(named) => &named.named,
Fields::Unnamed(_) => {
return Err(syn::Error::new_spanned(
struct_name,
"Tree derive only supports structs with named fields",
));
}
Fields::Unit => {
return Err(syn::Error::new_spanned(
struct_name,
"Tree derive does not support unit structs",
));
}
},
Data::Enum(_) => {
return Err(syn::Error::new_spanned(
struct_name,
"Tree derive does not support enums",
));
}
Data::Union(_) => {
return Err(syn::Error::new_spanned(
struct_name,
"Tree derive does not support unions",
));
}
};
let tree_attrs = parse_tree_attrs(input)?;
let mut label_field: Option<Ident> = None;
let mut children_field: Option<Ident> = None;
let mut leaf_fields: Vec<Ident> = Vec::new();
for field in fields.iter() {
let ident = named_field_ident(field)?.clone();
let kind = parse_tree_field_attrs(field)?;
match kind {
TreeFieldKind::Label => {
if label_field.is_some() {
return Err(syn::Error::new_spanned(
&ident,
"only one field can be marked #[tree(label)]",
));
}
label_field = Some(ident);
}
TreeFieldKind::Children => {
if children_field.is_some() {
return Err(syn::Error::new_spanned(
&ident,
"only one field can be marked #[tree(children)]",
));
}
children_field = Some(ident);
}
TreeFieldKind::Leaf => {
leaf_fields.push(ident);
}
TreeFieldKind::None => {
}
}
}
let label_ident = label_field.ok_or_else(|| {
syn::Error::new_spanned(
struct_name,
"Tree derive requires exactly one field marked #[tree(label)]",
)
})?;
let children_ident = children_field.ok_or_else(|| {
syn::Error::new_spanned(
struct_name,
"Tree derive requires exactly one field marked #[tree(children)]",
)
})?;
let style_setup = if let Some(ref lit) = tree_attrs.style {
let val = lit.value();
quote! {
if let Ok(s) = gilt::style::Style::parse(#val) {
tree.style = s;
}
}
} else {
quote! {}
};
let guide_style_setup = if let Some(ref lit) = tree_attrs.guide_style {
let val = lit.value();
quote! {
if let Ok(s) = gilt::style::Style::parse(#val) {
tree.guide_style = s;
}
}
} else {
quote! {}
};
let leaf_additions: Vec<proc_macro2::TokenStream> = leaf_fields
.iter()
.map(|ident| {
let leaf_label = crate::shared::snake_to_title_case(&ident.to_string());
quote! {
{
let leaf_text = format!("{}: {}", #leaf_label, self.#ident);
let leaf_label_text = gilt::text::Text::from(leaf_text.as_str());
tree.add(leaf_label_text);
}
}
})
.collect();
let expanded = quote! {
impl #struct_name {
pub fn to_tree(&self) -> gilt::tree::Tree {
let label_text = gilt::text::Text::from(self.#label_ident.to_string().as_str());
let mut tree = gilt::tree::Tree::new(label_text);
#style_setup
#guide_style_setup
#(#leaf_additions)*
for child in &self.#children_ident {
let child_tree = child.to_tree();
tree.children.push(child_tree);
}
tree
}
}
};
Ok(expanded)
}