use once_cell::sync::Lazy;
use regex::Regex;
use syn::{
braced,
parse::{Parse, ParseStream},
punctuated::Punctuated,
token, Attribute, Error, Field, Ident, LitStr, Result, Token, Visibility,
};
pub struct ItemStruct {
_attrs: Vec<Attribute>,
_vis: Visibility,
_struct_token: Token![struct],
pub ident: Ident,
_brace_token: token::Brace,
pub fields: Punctuated<Field, Token![,]>,
}
impl Parse for ItemStruct {
fn parse(input: ParseStream) -> Result<Self> {
let content;
Ok(ItemStruct {
_attrs: input.call(Attribute::parse_outer)?,
_vis: input.parse()?,
_struct_token: input.parse()?,
ident: input.parse()?,
_brace_token: braced!(content in input),
fields: content.parse_terminated(Field::parse_named, Token![,])?,
})
}
}
pub struct AttrList {
pub options: Punctuated<AttrOption, Token![,]>,
}
impl Parse for AttrList {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
options: input.parse_terminated(AttrOption::parse, Token![,])?,
})
}
}
pub enum AttrOption {
Rename(RenameAttr),
Ignore(IgnoreAttr),
}
impl AttrOption {
pub fn as_rename(&self) -> Option<&RenameAttr> {
if let Self::Rename(v) = self {
Some(v)
} else {
None
}
}
pub fn as_ignore(&self) -> Option<&IgnoreAttr> {
if let Self::Ignore(v) = self {
Some(v)
} else {
None
}
}
}
impl From<RenameAttr> for AttrOption {
fn from(v: RenameAttr) -> Self {
Self::Rename(v)
}
}
impl From<IgnoreAttr> for AttrOption {
fn from(v: IgnoreAttr) -> Self {
Self::Ignore(v)
}
}
pub struct RenameAttr {
pub ident: Ident,
_eq_token: Token![=],
_lit: LitStr,
pub rename: String,
}
pub struct IgnoreAttr {
pub ident: Ident,
}
impl Parse for AttrOption {
fn parse(input: ParseStream) -> Result<Self> {
let ident: Ident = input.parse()?;
let attr: Self = match ident.to_string().as_str() {
"rename" => {
static NAME_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[[:word:]]+$").unwrap());
let eq_token = input.parse()?;
let lit: LitStr = input.parse()?;
let rename = lit.value();
NAME_REGEX
.find(&rename)
.ok_or_else(|| Error::new(lit.span(), "invalid name"))?;
RenameAttr {
ident,
_eq_token: eq_token,
_lit: lit,
rename,
}
.into()
}
"ignore" => IgnoreAttr { ident }.into(),
name => {
return Err(Error::new(
ident.span(),
format!("invalid attribute '{}'", name),
));
}
};
Ok(attr)
}
}