mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
lofty_attr: cleanup
This commit is contained in:
parent
5cd1dfc99c
commit
364b9a741c
2 changed files with 123 additions and 121 deletions
|
@ -1,12 +1,10 @@
|
|||
mod util;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||
use std::fmt::Display;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Fields, Ident, Lit, Meta,
|
||||
MetaList, NestedMeta, Type,
|
||||
};
|
||||
use syn::{parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Fields, Ident, Type};
|
||||
|
||||
const LOFTY_FILE_TYPES: [&str; 10] = [
|
||||
"AIFF", "APE", "FLAC", "MPEG", "MP4", "Opus", "Vorbis", "Speex", "WAV", "WavPack",
|
||||
|
@ -34,7 +32,7 @@ pub fn lofty_file(input: TokenStream) -> TokenStream {
|
|||
fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::TokenStream {
|
||||
macro_rules! bail {
|
||||
($errors:ident, $span:expr, $msg:literal) => {
|
||||
$errors.push(err($span, $msg));
|
||||
$errors.push(util::err($span, $msg));
|
||||
return proc_macro2::TokenStream::new();
|
||||
};
|
||||
}
|
||||
|
@ -57,7 +55,7 @@ fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::Token
|
|||
|
||||
let impl_audiofile = should_impl_audiofile(&input.attrs);
|
||||
|
||||
let read_fn = match get_attr("read_fn", &input.attrs) {
|
||||
let read_fn = match util::get_attr("read_fn", &input.attrs) {
|
||||
Some(rfn) => rfn,
|
||||
_ if impl_audiofile => {
|
||||
bail!(
|
||||
|
@ -73,7 +71,7 @@ fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::Token
|
|||
|
||||
let file_type = match opt_file_type(struct_name.to_string()) {
|
||||
Some(ft) => ft,
|
||||
_ => match get_attr("file_type", &input.attrs) {
|
||||
_ => match util::get_attr("file_type", &input.attrs) {
|
||||
Some(rfn) => rfn,
|
||||
_ => {
|
||||
bail!(
|
||||
|
@ -91,20 +89,9 @@ fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::Token
|
|||
};
|
||||
|
||||
if tag_fields.is_empty() {
|
||||
errors.push(err(input.ident.span(), "Struct has no tag fields"));
|
||||
errors.push(util::err(input.ident.span(), "Struct has no tag fields"));
|
||||
}
|
||||
|
||||
let properties_field = if let Some(field) = properties_field {
|
||||
field
|
||||
} else {
|
||||
bail!(errors, input.ident.span(), "Struct has no properties field");
|
||||
};
|
||||
|
||||
let properties_field_ty = &properties_field.ty;
|
||||
let assert_properties_impl = quote_spanned! {properties_field_ty.span()=>
|
||||
struct _AssertIntoFileProperties where #properties_field_ty: std::convert::Into<lofty::FileProperties>;
|
||||
};
|
||||
|
||||
let assert_tag_impl_into = tag_fields.iter().enumerate().map(|(i, f)| {
|
||||
let name = format_ident!("_AssertTagExt{}", i);
|
||||
let field_ty = &f.ty;
|
||||
|
@ -125,6 +112,17 @@ fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::Token
|
|||
|
||||
let tag_type = tag_fields.iter().map(|f| &f.tag_type);
|
||||
|
||||
let properties_field = if let Some(field) = properties_field {
|
||||
field
|
||||
} else {
|
||||
bail!(errors, input.ident.span(), "Struct has no properties field");
|
||||
};
|
||||
|
||||
let properties_field_ty = &properties_field.ty;
|
||||
let assert_properties_impl = quote_spanned! {properties_field_ty.span()=>
|
||||
struct _AssertIntoFileProperties where #properties_field_ty: std::convert::Into<lofty::FileProperties>;
|
||||
};
|
||||
|
||||
let audiofile_impl = if impl_audiofile {
|
||||
quote! {
|
||||
impl lofty::AudioFile for #struct_name {
|
||||
|
@ -215,10 +213,10 @@ fn get_fields<'a>(
|
|||
for field in &data.fields {
|
||||
let name = field.ident.clone().unwrap();
|
||||
if name.to_string().ends_with("_tag") {
|
||||
let tag_type = match get_attr("tag_type", &field.attrs) {
|
||||
let tag_type = match util::get_attr("tag_type", &field.attrs) {
|
||||
Some(tt) => tt,
|
||||
_ => {
|
||||
errors.push(err(field.span(), "Field has no `tag_type` attribute"));
|
||||
errors.push(util::err(field.span(), "Field has no `tag_type` attribute"));
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
@ -227,16 +225,16 @@ fn get_fields<'a>(
|
|||
.attrs
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter_map(|a| get_attr_list("cfg", &a).map(|_| a))
|
||||
.filter_map(|a| util::get_attr_list("cfg", &a).map(|_| a))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let option_unwrapped = extract_type_from_option(&field.ty);
|
||||
let option_unwrapped = util::extract_type_from_option(&field.ty);
|
||||
// `option_unwrapped` will be `Some` if the type was wrapped in an `Option`
|
||||
let needs_option = option_unwrapped.is_some();
|
||||
|
||||
let contents = FieldContents {
|
||||
name,
|
||||
getter_name: get_attr("getter", &field.attrs),
|
||||
getter_name: util::get_attr("getter", &field.attrs),
|
||||
ty: option_unwrapped.unwrap_or_else(|| field.ty.clone()),
|
||||
tag_type,
|
||||
needs_option,
|
||||
|
@ -272,31 +270,9 @@ fn opt_file_type(struct_name: String) -> Option<proc_macro2::TokenStream> {
|
|||
None
|
||||
}
|
||||
|
||||
fn get_attr(name: &str, attrs: &[Attribute]) -> Option<proc_macro2::TokenStream> {
|
||||
for attr in attrs {
|
||||
if let Some(list) = get_attr_list("lofty", attr) {
|
||||
if let Some(NestedMeta::Meta(Meta::NameValue(mnv))) = list.nested.first() {
|
||||
if mnv
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.expect("path shouldn't be empty")
|
||||
.ident == name
|
||||
{
|
||||
if let Lit::Str(lit_str) = &mnv.lit {
|
||||
return Some(lit_str.parse::<proc_macro2::TokenStream>().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn should_impl_audiofile(attrs: &[Attribute]) -> bool {
|
||||
for attr in attrs {
|
||||
if has_path_attr(attr, "no_audiofile_impl") {
|
||||
if util::has_path_attr(attr, "no_audiofile_impl") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -304,28 +280,6 @@ fn should_impl_audiofile(attrs: &[Attribute]) -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
fn has_path_attr(attr: &Attribute, name: &str) -> bool {
|
||||
if let Some(list) = get_attr_list("lofty", attr) {
|
||||
if let Some(NestedMeta::Meta(Meta::Path(p))) = list.nested.first() {
|
||||
if p.is_ident(name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn get_attr_list(path: &str, attr: &Attribute) -> Option<MetaList> {
|
||||
if attr.path.is_ident(path) {
|
||||
if let Ok(Meta::List(list)) = attr.parse_meta() {
|
||||
return Some(list);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_getters<'a>(
|
||||
tag_fields: &'a [FieldContents],
|
||||
struct_name: &'a Ident,
|
||||
|
@ -346,10 +300,6 @@ fn get_getters<'a>(
|
|||
let field_name = &f.name;
|
||||
let field_ty = &f.ty;
|
||||
|
||||
let assert_field_ty_default = quote_spanned! {f.name.span()=>
|
||||
struct _AssertDefault where #field_ty: core::default::Default;
|
||||
};
|
||||
|
||||
let ref_access = if f.needs_option {
|
||||
quote! {self.#field_name.as_ref()}
|
||||
} else {
|
||||
|
@ -369,6 +319,10 @@ fn get_getters<'a>(
|
|||
let remover = if f.needs_option {
|
||||
quote! {self.#field_name = None;}
|
||||
} else {
|
||||
let assert_field_ty_default = quote_spanned! {f.name.span()=>
|
||||
struct _AssertDefault where #field_ty: core::default::Default;
|
||||
};
|
||||
|
||||
quote! {
|
||||
#assert_field_ty_default
|
||||
self.#field_name = <#field_ty>::default();
|
||||
|
@ -397,50 +351,3 @@ fn get_getters<'a>(
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn
|
||||
fn extract_type_from_option(ty: &Type) -> Option<Type> {
|
||||
use syn::{GenericArgument, Path, PathArguments, PathSegment};
|
||||
|
||||
fn extract_type_path(ty: &Type) -> Option<&Path> {
|
||||
match *ty {
|
||||
Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
|
||||
let idents_of_path = path
|
||||
.segments
|
||||
.iter()
|
||||
.into_iter()
|
||||
.fold(String::new(), |mut acc, v| {
|
||||
acc.push_str(&v.ident.to_string());
|
||||
acc.push('|');
|
||||
acc
|
||||
});
|
||||
vec!["Option|", "std|option|Option|", "core|option|Option|"]
|
||||
.into_iter()
|
||||
.find(|s| idents_of_path == *s)
|
||||
.and_then(|_| path.segments.last())
|
||||
}
|
||||
|
||||
extract_type_path(ty)
|
||||
.and_then(extract_option_segment)
|
||||
.and_then(|path_seg| {
|
||||
let type_params = &path_seg.arguments;
|
||||
// It should have only on angle-bracketed param ("<String>"):
|
||||
match *type_params {
|
||||
PathArguments::AngleBracketed(ref params) => params.args.first(),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.and_then(|generic_arg| match *generic_arg {
|
||||
GenericArgument::Type(ref ty) => Some(ty.clone()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn err<T: Display>(span: Span, error: T) -> syn::Error {
|
||||
syn::Error::new(span, error)
|
||||
}
|
||||
|
|
95
lofty_attr/src/util.rs
Normal file
95
lofty_attr/src/util.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::{Attribute, Lit, Meta, MetaList, NestedMeta, Type};
|
||||
|
||||
pub(crate) fn get_attr(name: &str, attrs: &[Attribute]) -> Option<proc_macro2::TokenStream> {
|
||||
for attr in attrs {
|
||||
if let Some(list) = get_attr_list("lofty", attr) {
|
||||
if let Some(NestedMeta::Meta(Meta::NameValue(mnv))) = list.nested.first() {
|
||||
if mnv
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.expect("path shouldn't be empty")
|
||||
.ident == name
|
||||
{
|
||||
if let Lit::Str(lit_str) = &mnv.lit {
|
||||
return Some(lit_str.parse::<proc_macro2::TokenStream>().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn has_path_attr(attr: &Attribute, name: &str) -> bool {
|
||||
if let Some(list) = get_attr_list("lofty", attr) {
|
||||
if let Some(NestedMeta::Meta(Meta::Path(p))) = list.nested.first() {
|
||||
if p.is_ident(name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn get_attr_list(path: &str, attr: &Attribute) -> Option<MetaList> {
|
||||
if attr.path.is_ident(path) {
|
||||
if let Ok(Meta::List(list)) = attr.parse_meta() {
|
||||
return Some(list);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn
|
||||
pub(crate) fn extract_type_from_option(ty: &Type) -> Option<Type> {
|
||||
use syn::{GenericArgument, Path, PathArguments, PathSegment};
|
||||
|
||||
fn extract_type_path(ty: &Type) -> Option<&Path> {
|
||||
match *ty {
|
||||
Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
|
||||
let idents_of_path = path
|
||||
.segments
|
||||
.iter()
|
||||
.into_iter()
|
||||
.fold(String::new(), |mut acc, v| {
|
||||
acc.push_str(&v.ident.to_string());
|
||||
acc.push('|');
|
||||
acc
|
||||
});
|
||||
vec!["Option|", "std|option|Option|", "core|option|Option|"]
|
||||
.into_iter()
|
||||
.find(|s| idents_of_path == *s)
|
||||
.and_then(|_| path.segments.last())
|
||||
}
|
||||
|
||||
extract_type_path(ty)
|
||||
.and_then(extract_option_segment)
|
||||
.and_then(|path_seg| {
|
||||
let type_params = &path_seg.arguments;
|
||||
// It should have only on angle-bracketed param ("<String>"):
|
||||
match *type_params {
|
||||
PathArguments::AngleBracketed(ref params) => params.args.first(),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.and_then(|generic_arg| match *generic_arg {
|
||||
GenericArgument::Type(ref ty) => Some(ty.clone()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn err<T: Display>(span: Span, error: T) -> syn::Error {
|
||||
syn::Error::new(span, error)
|
||||
}
|
Loading…
Reference in a new issue