bind-conf/bind_conf_derive/src/attr.rs

248 lines
6.1 KiB
Rust

use syn;
use proc_macro2;
use quote::{quote, format_ident};
#[derive(Debug)]
pub struct Global {
fallback: Option<String>,
path: Option<String>,
name: syn::Ident,
}
impl Global {
pub fn from_ast(ast: &syn::DeriveInput) -> Self {
let mut fallback = None;
let mut path = None;
let meta_iter = ast.attrs.iter()
.filter_map(get_meta_item)
.flatten();
for meta in meta_iter {
match meta {
syn::Meta::NameValue(ref m) if m.path.is_ident("fallback") => {
fallback = get_lit_str(&m.lit);
},
syn::Meta::NameValue(ref m) if m.path.is_ident("path") => {
path = get_lit_str(&m.lit);
},
_ => ()
}
}
Global {
fallback,
path,
name: ast.ident.clone()
}
}
pub fn gen_use_bind_conf(&self) -> proc_macro2::TokenStream {
// FIXME: doesn't work with multiple segments path like my_crate::bind_conf
let path = format_ident!("{}", self.path.as_ref().unwrap_or(&"bind_conf".into()));
quote! {
use #path::{Parser, Error, Result};
}
}
pub fn gen_enum_fallback(&self) -> proc_macro2::TokenStream {
if let Some(ref fallback) = self.fallback {
let fallback_variant = format_ident!("{}", fallback);
quote! {
{
pa.discard_statement()?;
Ok(Self::#fallback_variant)
}
}
} else {
quote! {
Err(Error::UnknownVariant)
}
}
}
pub fn name(&self) -> &syn::Ident {
&self.name
}
}
pub trait NamedIdent {
fn alias(&self) -> Vec<String>;
fn rename(&self) -> Option<String>;
fn rename_ident(ident: &syn::Ident) -> String;
fn names(&self, ident: &syn::Ident) -> Vec<String> {
let mut names = self.alias();
let name = if let Some(new_name) = self.rename() {
new_name
} else {
Self::rename_ident(ident)
};
if !names.contains(&name) {
names.push(name.to_string());
}
names
}
}
#[derive(Debug)]
pub struct FieldAttr {
inline: bool,
rename: Option<String>,
alias: Vec<String>,
default: Option<syn::ExprPath>,
}
impl NamedIdent for FieldAttr {
fn rename(&self) -> Option<String> {
self.rename.clone()
}
fn alias(&self) -> Vec<String> {
self.alias.clone()
}
fn rename_ident(ident: &syn::Ident) -> String {
ident.to_string().as_str().replace("_", "-").into()
}
}
impl FieldAttr {
pub fn from_ast(field: &syn::Field) -> Self {
let mut inline = false;
let mut rename = None;
let mut alias = Vec::new();
let mut default = None;
let meta_iter = field.attrs.iter()
.filter_map(get_meta_item)
.flatten();
for meta in meta_iter {
match meta {
syn::Meta::Path(ref p) if p.is_ident("inline") => {
inline = true;
},
syn::Meta::NameValue(ref m) if m.path.is_ident("rename") => {
rename = get_lit_str(&m.lit);
},
syn::Meta::NameValue(ref m) if m.path.is_ident("alias") => {
if let Some(s) = get_lit_str(&m.lit) {
alias.push(s);
}
},
syn::Meta::NameValue(ref m) if m.path.is_ident("default") => {
default = get_lit_str(&m.lit).map(|s| syn::parse_str(s.as_ref()).ok()).flatten();
}
_ => ()
}
}
FieldAttr {
inline,
rename,
alias,
default,
}
}
pub fn inline(&self) -> bool {
self.inline
}
pub fn default(&self) -> Option<syn::ExprPath> {
self.default.clone()
}
}
pub struct VariantAttr {
rename: Option<String>,
alias: Vec<String>,
}
impl NamedIdent for VariantAttr {
fn rename(&self) -> Option<String> {
self.rename.clone()
}
fn alias(&self) -> Vec<String> {
self.alias.clone()
}
fn rename_ident(ident: &syn::Ident) -> String {
let ident_name = ident.to_string();
let mut res = String::new();
for (i, ch) in ident_name.char_indices() {
if i > 0 && ch.is_ascii_uppercase() {
res.push('-');
}
res.push(ch.to_ascii_lowercase());
}
res
}
}
impl VariantAttr {
pub fn from_ast(variant: &syn::Variant) -> Self {
let mut rename = None;
let mut alias = Vec::new();
let meta_iter = variant.attrs.iter()
.filter_map(get_meta_item)
.flatten();
for meta in meta_iter {
match meta {
syn::Meta::NameValue(ref m) if m.path.is_ident("rename") => {
rename = get_lit_str(&m.lit);
},
syn::Meta::NameValue(ref m) if m.path.is_ident("alias") => {
if let Some(s) = get_lit_str(&m.lit) {
alias.push(s);
}
}
_ => ()
}
}
VariantAttr {
rename,
alias,
}
}
}
fn get_meta_item(attr: &syn::Attribute) -> Option<Vec<syn::Meta>> {
if attr.path.is_ident("parse") {
match attr.parse_meta() {
Ok(syn::Meta::List(ref meta_list)) => Some(
meta_list.nested.iter().filter_map(|nested| {
match nested {
syn::NestedMeta::Meta(meta) => Some(meta),
_ => None,
}
}).cloned().collect()
),
_ => None
}
} else {
None
}
}
fn get_lit_str(lit: &syn::Lit) -> Option<String> {
if let syn::Lit::Str(lit_str) = lit {
Some(lit_str.value())
} else {
None
}
}