lofty_attr: Update to syn 2

This commit is contained in:
Serial 2023-05-08 11:09:51 -04:00 committed by Alex
parent fcffc19cae
commit 77a05f337f
4 changed files with 152 additions and 112 deletions

View file

@ -10,9 +10,9 @@ readme = "README.md"
include = ["src", "Cargo.toml", "../LICENSE-*"]
[dependencies]
syn = { version = "1.0.95", features = ["full"] }
quote = "1.0.18"
proc-macro2 = "1.0.39"
syn = { version = "2.0.15", features = ["full", "parsing"] }
quote = "1.0.26"
proc-macro2 = "1.0.56"
[lib]
proc-macro = true

View file

@ -38,9 +38,11 @@ mod lofty_file;
mod lofty_tag;
mod util;
use lofty_tag::LoftyTagAttribute;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, Data, DataStruct, DeriveInput, Fields, ItemStruct};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, ItemStruct};
/// Creates a file usable by Lofty
///
@ -76,13 +78,12 @@ pub fn lofty_file(input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
#[doc(hidden)]
pub fn tag(args_input: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args_input as AttributeArgs);
let args = parse_macro_input!(args_input as LoftyTagAttribute);
let input = parse_macro_input!(input as ItemStruct);
let mut errors = Vec::new();
let ret = lofty_tag::parse(args, input, &mut errors);
let ret = lofty_tag::create(args, input);
finish(&ret, &errors)
finish(&ret, &[])
}
fn finish(ret: &proc_macro2::TokenStream, errors: &[syn::Error]) -> TokenStream {

View file

@ -1,102 +1,97 @@
use crate::util::bail;
use proc_macro2::Span;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{AttributeArgs, ItemStruct, Lit, Meta, NestedMeta, Path};
use syn::{
Error, Expr, ExprLit, ItemStruct, Lit, LitStr, Meta, MetaList, MetaNameValue, Path, Result,
Token,
};
enum SupportedFormat {
Full(Path),
ReadOnly(Path),
}
pub(crate) fn parse(
attr: AttributeArgs,
input: ItemStruct,
errors: &mut Vec<syn::Error>,
) -> proc_macro2::TokenStream {
let mut desc = None;
let mut supported_formats = Vec::new();
let mut read_only_encountered = false;
pub(crate) struct LoftyTagAttribute {
description: LitStr,
supported_formats: Vec<SupportedFormat>,
}
for nested_meta in attr {
match nested_meta {
NestedMeta::Meta(Meta::NameValue(mnv)) if mnv.path.is_ident("description") => {
if desc.is_some() {
bail!(errors, mnv.span(), "Duplicate `description` entry");
}
impl Parse for LoftyTagAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let mut description = None;
let mut supported_formats = Vec::new();
if let Lit::Str(s) = &mnv.lit {
desc = Some(s.value());
continue;
}
let start_span = input.span();
bail!(
errors,
Span::call_site(),
"Invalid `description` entry, expected string value"
);
},
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("supported_formats") => {
for nested in list.nested {
if let NestedMeta::Meta(meta) = nested {
match meta {
Meta::Path(path) => {
supported_formats.push(SupportedFormat::Full(path.clone()));
continue;
},
Meta::List(list) if list.path.is_ident("read_only") => {
if read_only_encountered {
bail!(errors, list.path.span(), "Duplicate `read_only` entry");
}
read_only_encountered = true;
for item in list.nested.iter() {
if let NestedMeta::Meta(Meta::Path(path)) = item {
supported_formats
.push(SupportedFormat::ReadOnly(path.clone()));
continue;
}
}
continue;
},
_ => {},
}
let args = Punctuated::<Meta, Token![,]>::parse_separated_nonempty(input)?;
for nested_meta in args {
match nested_meta {
Meta::NameValue(mnv) if mnv.path.is_ident("description") => {
if description.is_some() {
return Err(Error::new(mnv.span(), "Duplicate `description` entry"));
}
}
continue;
},
_ => {
bail!(
errors,
nested_meta.span(),
"Unexpected input, check the format of the arguments"
);
},
description = Some(parse_description(mnv)?);
},
Meta::List(list) if list.path.is_ident("supported_formats") => {
parse_supported_formats(list, &mut supported_formats)?;
},
_ => {
return Err(Error::new(
nested_meta.span(),
"Unexpected input, check the format of the arguments",
))
},
}
}
}
if description.is_none() {
return Err(Error::new(start_span, "No description provided"));
}
Ok(Self {
description: description.unwrap(),
supported_formats,
})
}
}
pub(crate) fn create(
lofty_tag_attribute: LoftyTagAttribute,
input: ItemStruct,
) -> proc_macro2::TokenStream {
let ident = &input.ident;
let supported_types_iter = supported_formats.iter().map(|format| match format {
SupportedFormat::Full(path) => format!(
"* [`FileType::{ft}`](crate::FileType::{ft})\n",
ft = path.get_ident().unwrap()
),
SupportedFormat::ReadOnly(path) => format!(
"* [`FileType::{ft}`](crate::FileType::{ft}) **(READ ONLY)**\n",
ft = path.get_ident().unwrap()
),
});
let flattened_file_types = supported_formats.iter().map(|format| match format {
SupportedFormat::Full(path) | SupportedFormat::ReadOnly(path) => path,
});
let read_only_file_types = supported_formats.iter().filter_map(|format| match format {
SupportedFormat::ReadOnly(path) => Some(path),
_ => None,
});
let desc = lofty_tag_attribute.description;
let supported_types_iter =
lofty_tag_attribute
.supported_formats
.iter()
.map(|format| match format {
SupportedFormat::Full(path) => format!(
"* [`FileType::{ft}`](crate::FileType::{ft})\n",
ft = path.get_ident().unwrap()
),
SupportedFormat::ReadOnly(path) => format!(
"* [`FileType::{ft}`](crate::FileType::{ft}) **(READ ONLY)**\n",
ft = path.get_ident().unwrap()
),
});
let flattened_file_types =
lofty_tag_attribute
.supported_formats
.iter()
.map(|format| match format {
SupportedFormat::Full(path) | SupportedFormat::ReadOnly(path) => path,
});
let read_only_file_types = lofty_tag_attribute
.supported_formats
.iter()
.filter_map(|format| match format {
SupportedFormat::ReadOnly(path) => Some(path),
_ => None,
});
quote! {
use crate::_this_is_internal;
@ -119,3 +114,41 @@ pub(crate) fn parse(
}
}
}
fn parse_description(name_value: MetaNameValue) -> Result<LitStr> {
match name_value.value {
Expr::Lit(ExprLit {
lit: Lit::Str(lit_str),
..
}) => Ok(lit_str),
_ => Err(Error::new(
name_value.span(),
"Invalid `description` entry, expected string value",
)),
}
}
fn parse_supported_formats(
meta_list: MetaList,
supported_formats: &mut Vec<SupportedFormat>,
) -> Result<()> {
let mut read_only_encountered = false;
meta_list.parse_nested_meta(|meta| {
if meta.path.is_ident("read_only") {
if read_only_encountered {
return Err(meta.error("Duplicate `read_only` entry"));
}
read_only_encountered = true;
meta.parse_nested_meta(|nested_meta| {
supported_formats.push(SupportedFormat::ReadOnly(nested_meta.path));
Ok(())
})?;
} else {
supported_formats.push(SupportedFormat::Full(meta.path));
}
Ok(())
})
}

View file

@ -1,10 +1,10 @@
use std::fmt::Display;
use proc_macro2::Span;
use syn::{Attribute, Lit, Meta, MetaList, NestedMeta, Type};
use syn::{Attribute, Error, LitStr, Meta, MetaList, Type};
macro_rules! bail {
($errors:ident, $span:expr, $msg:literal) => {
($errors:ident, $span:expr, $msg:expr) => {
$errors.push(crate::util::err($span, $msg));
return proc_macro2::TokenStream::new();
};
@ -13,43 +13,49 @@ macro_rules! bail {
pub(crate) use bail;
pub(crate) fn get_attr(name: &str, attrs: &[Attribute]) -> Option<proc_macro2::TokenStream> {
let mut found = None;
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());
}
let res = list.parse_nested_meta(|meta| {
if meta.path.is_ident(name) {
let value = meta.value()?;
let value_str: LitStr = value.parse()?;
found = Some(value_str.parse::<proc_macro2::TokenStream>().unwrap());
return Ok(());
}
Err(meta.error(""))
});
if res.is_ok() {
return found;
}
}
}
None
found
}
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;
let res = list.parse_nested_meta(|meta| {
if meta.path.is_ident(name) {
return Ok(());
}
}
Err(Error::new(Span::call_site(), ""))
});
return res.is_ok();
}
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);
if attr.path().is_ident(path) {
if let Meta::List(list) = &attr.meta {
return Some(list.clone());
}
}