lofty_attr: cleanup

This commit is contained in:
Serial 2022-09-24 03:43:56 -04:00
parent 5cd1dfc99c
commit 364b9a741c
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
2 changed files with 123 additions and 121 deletions

View file

@ -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
View 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)
}