use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use std::convert::TryFrom;
use syn::spanned::Spanned;
use syn::{Attribute, Error, Lit, Meta, NestedMeta, Path, Result};
#[derive(Debug, Clone)]
pub enum Value {
Ident(Ident),
Lit(Lit),
}
impl ToTokens for Value {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Value::Ident(ident) => ident.to_tokens(tokens),
Value::Lit(lit) => lit.to_tokens(tokens),
}
}
}
#[derive(Debug, Clone)]
pub struct Attr {
pub path: Path,
pub values: Vec<Value>,
}
impl Attr {
pub fn new(path: Path, values: Vec<Value>) -> Self {
Self { path, values }
}
}
impl Attr {
#[allow(dead_code)]
pub fn parse_value<T>(&self, f: impl FnOnce(&Value) -> Result<T>) -> Result<T> {
if self.values.is_empty() {
return Err(Error::new(self.span(), "Attribute input must not be empty"));
}
if self.values.len() > 1 {
return Err(Error::new(
self.span(),
"Attribute input must not exceed more than one argument",
));
}
f(&self.values[0])
}
#[allow(dead_code)]
pub fn parse_identifiers(&self) -> Result<Vec<Ident>> {
self.values
.iter()
.map(|v| match v {
Value::Ident(ident) => Ok(ident.clone()),
Value::Lit(lit) => Err(Error::new(lit.span(), "Literals are forbidden")),
})
.collect::<Result<Vec<_>>>()
}
#[allow(dead_code)]
pub fn parse_identifier(&self) -> Result<Ident> {
self.parse_value(|value| {
Ok(match value {
Value::Ident(ident) => ident.clone(),
_ => return Err(Error::new(value.span(), "Argument must be an identifier")),
})
})
}
#[allow(dead_code)]
pub fn parse_string(&self) -> Result<String> {
self.parse_value(|value| {
Ok(match value {
Value::Lit(Lit::Str(s)) => s.value(),
_ => return Err(Error::new(value.span(), "Argument must be a string")),
})
})
}
#[allow(dead_code)]
pub fn parse_bool(&self) -> Result<bool> {
self.parse_value(|value| {
Ok(match value {
Value::Lit(Lit::Bool(b)) => b.value,
_ => return Err(Error::new(value.span(), "Argument must be a boolean")),
})
})
}
}
impl ToTokens for Attr {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Attr { path, values } = self;
tokens.extend(if values.is_empty() {
quote::quote!(#[#path])
} else {
quote::quote!(#[#path(#(#values)*,)])
});
}
}
impl TryFrom<&Attribute> for Attr {
type Error = Error;
fn try_from(attr: &Attribute) -> Result<Self> {
parse_attribute(attr)
}
}
pub fn parse_attribute(attr: &Attribute) -> Result<Attr> {
let meta = attr.parse_meta()?;
match meta {
Meta::Path(p) => Ok(Attr::new(p, Vec::new())),
Meta::List(l) => {
let path = l.path;
let values = l
.nested
.into_iter()
.map(|m| match m {
NestedMeta::Lit(lit) => Ok(Value::Lit(lit)),
NestedMeta::Meta(m) => match m {
Meta::Path(p) => Ok(Value::Ident(p.get_ident().unwrap().clone())),
_ => Err(Error::new(
m.span(),
"Nested lists or name values are not supported",
)),
},
})
.collect::<Result<Vec<_>>>()?;
Ok(Attr::new(path, values))
}
Meta::NameValue(nv) => Ok(Attr::new(nv.path, vec![Value::Lit(nv.lit)])),
}
}