use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::punctuated::Punctuated;
use syn::{Error, Path, Token, Lit, LitStr};
use proc_macro2::Span;
pub trait FromMeta: Sized {
fn from_meta(a: &MetaValue) -> Result<Self, Error>;
}
#[derive(Clone)]
pub enum MetaValue {
Path(Path),
NameValue(MetaNameValue),
List(MetaList),
Lit(Lit),
}
impl MetaValue {
pub fn path(&self) -> Result<&Path, Error> {
match self {
Self::Path(p) => Ok(p),
Self::NameValue(nv) => Err(
Error::new(
nv.eq.span(),
"unexpected =; expected a naked path",
)
),
Self::List(list) => Err(
Error::new(
list.paren.map(|p| p.span).unwrap_or_else(Span::call_site),
"unexpected (...); expected a naked path",
)
),
Self::Lit(lit) => Err(
Error::new(
lit.span(),
"expected a naked path",
)
),
}
}
pub fn name_value(&self) -> Result<&MetaNameValue, Error> {
match self {
Self::NameValue(nv) => Ok(nv),
Self::Path(p) => Err(
Error::new(
p.span(),
"expected =",
)
),
Self::List(list) => Err(
Error::new(
list.paren.map(|p| p.span).unwrap_or_else(Span::call_site),
"expected =",
)
),
Self::Lit(lit) => Err(
Error::new(
lit.span(),
"unexpected literal; expected =",
)
),
}
}
pub fn lit(&self) -> Result<&Lit, Error> {
match self {
Self::Lit(lit) => Ok(lit),
Self::Path(p) => Err(
Error::new(
p.span(),
"expected a literal",
)
),
Self::List(list) => Err(
Error::new(
list.name.span(),
"expected a literal",
)
),
Self::NameValue(nv) => Err(
Error::new(
nv.name.span(),
"expected a literal",
)
),
}
}
pub fn list(&self) -> Result<&MetaList, Error> {
match self {
Self::List(list) => Ok(list),
Self::Path(p) => Err(
Error::new(
p.span(),
"expected a list",
)
),
Self::NameValue(nv) => Err(
Error::new(
nv.eq.span(),
"unexpected =; expected a list",
)
),
Self::Lit(lit) => Err(
Error::new(
lit.span(),
"expected a list",
)
),
}
}
pub fn value(&self) -> Result<&Lit, Error> {
match self {
Self::Lit(lit) => Ok(lit),
Self::NameValue(nv) => Ok(&nv.value),
Self::Path(p) => Err(
Error::new(
p.span(),
"expected a literal",
)
),
Self::List(list) => Err(
Error::new(
list.paren.map(|p| p.span).unwrap_or_else(Span::call_site),
"expected a literal",
)
),
}
}
pub fn name(&self) -> Option<&syn::Ident> {
let path = match self {
Self::Path(p) => p,
Self::List(list) => list.name.as_ref()?,
Self::NameValue(nv) => &nv.name,
_ => return None,
};
path.segments.last().map(|l| &l.ident)
}
}
impl From<Lit> for MetaValue {
fn from(l: Lit) -> MetaValue {
MetaValue::Lit(l)
}
}
impl From<MetaList> for MetaValue {
fn from(l: MetaList) -> MetaValue {
MetaValue::List(l)
}
}
impl Parse for MetaValue {
fn parse(p: ParseStream) -> Result<MetaValue, Error> {
if let Ok(name) = p.parse::<Path>() {
if p.peek(syn::token::Paren) {
let list;
Ok(MetaValue::List(
MetaList {
name: Some(name),
paren: Some(syn::parenthesized!(list in p)),
list: list.parse_terminated(Self::parse)?,
}
))
} else if p.peek(Token![=]) {
Ok(MetaValue::NameValue(
MetaNameValue {
name,
eq: p.parse()?,
value: p.parse()?,
}
))
} else {
Ok(MetaValue::Path(name))
}
} else {
p.parse::<Lit>().map(Into::into)
}
}
}
#[derive(Clone)]
pub struct MetaNameValue {
pub name: Path,
pub eq: Token![=],
pub value: Lit,
}
#[derive(Clone, Default)]
pub struct MetaList {
pub name: Option<Path>,
pub paren: Option<syn::token::Paren>,
pub list: Punctuated<MetaValue, Token![,]>,
}
impl MetaList {
pub fn get<T>(&self, name: &str) -> Option<Result<T, Error>>
where T:
FromMeta,
{
let item = self.list.iter()
.filter(|meta| meta.name().map(|n| n == name).unwrap_or(false))
.next()?;
Some(T::from_meta(&item))
}
pub fn parse_root_attr(p: ParseStream) -> Result<MetaList, Error> {
Ok(
MetaList {
name: None,
paren: None,
list: p.call(Punctuated::parse_terminated)?,
}
)
}
}
pub struct Meta<T>(pub T);
impl<T> Meta<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> std::ops::Deref for Meta<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> Parse for Meta<T>
where T:
FromMeta,
{
fn parse(p: ParseStream) -> Result<Meta<T>, Error> {
if p.is_empty() {
T::from_meta(&MetaList::default().into())
.map(|t| Meta(t))
} else {
p.call(MetaList::parse_root_attr)
.map(Into::into)
.and_then(|meta| T::from_meta(&meta))
.map(|t| Meta(t))
}
}
}
impl FromMeta for LitStr {
fn from_meta(meta: &MetaValue) -> Result<LitStr, Error> {
let value = meta.value()?;
match &value {
Lit::Str(lit) => Ok(lit.clone()),
_ => Err(
Error::new(
value.span(),
"expected a string literal",
)
),
}
}
}
impl<T> FromMeta for Option<T>
where T:
FromMeta,
{
fn from_meta(p: &MetaValue) -> Result<Option<T>, Error> {
Ok(Some(T::from_meta(p)?))
}
}