mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
lofty_attr: Update to syn 2
This commit is contained in:
parent
fcffc19cae
commit
77a05f337f
4 changed files with 152 additions and 112 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue