use proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{Error, ExprPath, Meta, NestedMeta};
const BASE_ATTR_PATH: &str = "druid";
const IGNORE_ATTR_PATH: &str = "ignore";
const SAME_FN_ATTR_PATH: &str = "same_fn";
#[derive(Debug)]
pub struct Fields {
pub kind: FieldKind,
fields: Vec<Field>,
}
#[derive(Debug, Clone)]
pub enum FieldKind {
Named,
Unnamed,
}
#[derive(Debug)]
pub enum FieldIdent {
Named(String),
Unnamed(usize),
}
#[derive(Debug)]
pub struct Field {
pub ident: FieldIdent,
pub ignore: bool,
pub same_fn: Option<ExprPath>,
}
impl Fields {
pub fn parse_ast(fields: &syn::Fields) -> Result<Self, Error> {
let kind = match fields {
syn::Fields::Named(_) => FieldKind::Named,
syn::Fields::Unnamed(_) | syn::Fields::Unit => FieldKind::Unnamed,
};
let fields = fields
.iter()
.enumerate()
.map(|(i, field)| Field::parse_ast(field, i))
.collect::<Result<Vec<_>, _>>()?;
Ok(Fields { kind, fields })
}
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Field> {
self.fields.iter()
}
}
impl Field {
pub fn parse_ast(field: &syn::Field, index: usize) -> Result<Self, Error> {
let ident = match field.ident.as_ref() {
Some(ident) => FieldIdent::Named(ident.to_string().trim_start_matches("r#").to_owned()),
None => FieldIdent::Unnamed(index),
};
let mut ignore = false;
let mut same_fn = None;
for attr in field
.attrs
.iter()
.filter(|attr| attr.path.is_ident(BASE_ATTR_PATH))
{
match attr.parse_meta()? {
Meta::List(meta) => {
for nested in meta.nested.iter() {
match nested {
NestedMeta::Meta(Meta::Path(path))
if path.is_ident(IGNORE_ATTR_PATH) =>
{
if ignore {
return Err(Error::new(nested.span(), "Duplicate attribute"));
}
ignore = true;
}
NestedMeta::Meta(Meta::NameValue(meta))
if meta.path.is_ident(SAME_FN_ATTR_PATH) =>
{
if same_fn.is_some() {
return Err(Error::new(meta.span(), "Duplicate attribute"));
}
let path = parse_lit_into_expr_path(&meta.lit)?;
same_fn = Some(path);
}
other => return Err(Error::new(other.span(), "Unknown attribute")),
}
}
}
other => {
return Err(Error::new(
other.span(),
"Expected attribute list (the form #[druid(one, two)])",
))
}
}
}
Ok(Field {
ident,
ignore,
same_fn,
})
}
pub fn ident_tokens(&self) -> TokenTree {
match self.ident {
FieldIdent::Named(ref s) => Ident::new(&s, Span::call_site()).into(),
FieldIdent::Unnamed(num) => Literal::usize_unsuffixed(num).into(),
}
}
pub fn ident_string(&self) -> String {
match self.ident {
FieldIdent::Named(ref s) => s.clone(),
FieldIdent::Unnamed(num) => num.to_string(),
}
}
pub fn same_fn_path_tokens(&self) -> TokenStream {
match self.same_fn {
Some(ref f) => quote!(#f),
None => {
let span = Span::call_site();
quote_spanned!(span=> druid::Data::same)
}
}
}
}
fn parse_lit_into_expr_path(lit: &syn::Lit) -> Result<ExprPath, Error> {
let string = if let syn::Lit::Str(lit) = lit {
lit
} else {
return Err(Error::new(
lit.span(),
"expected str, found... something else",
));
};
let tokens = syn::parse_str(&string.value())?;
syn::parse2(tokens)
}