mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
lofty_attr: Add tag
attribute macro to generate some docs
This commit is contained in:
parent
f3d9176a48
commit
efab7ca276
9 changed files with 137 additions and 115 deletions
|
@ -5,26 +5,13 @@ mod util;
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields};
|
||||
use syn::{parse_macro_input, AttributeArgs, Data, DataStruct, DeriveInput, Fields, ItemStruct};
|
||||
|
||||
/// Creates a file usable by Lofty
|
||||
///
|
||||
/// See [here](https://github.com/Serial-ATA/lofty-rs/tree/main/examples/custom_resolver) for an example of how to use it.
|
||||
#[proc_macro_derive(LoftyFile, attributes(lofty))]
|
||||
pub fn lofty_file(input: TokenStream) -> TokenStream {
|
||||
act(input, lofty_file::parse)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(LoftyTag, attributes(lofty))]
|
||||
#[doc(hidden)]
|
||||
pub fn lofty_tag(input: TokenStream) -> TokenStream {
|
||||
act(input, lofty_tag::parse)
|
||||
}
|
||||
|
||||
fn act(
|
||||
input: TokenStream,
|
||||
func: impl Fn(&DeriveInput, &DataStruct, &mut Vec<syn::Error>) -> proc_macro2::TokenStream,
|
||||
) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let data_struct = match input.data {
|
||||
|
@ -46,8 +33,24 @@ fn act(
|
|||
};
|
||||
|
||||
let mut errors = Vec::new();
|
||||
let ret = func(&input, data_struct, &mut errors);
|
||||
let ret = lofty_file::parse(&input, data_struct, &mut errors);
|
||||
|
||||
finish(ret, errors)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
#[doc(hidden)]
|
||||
pub fn tag(args_input: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(args_input as AttributeArgs);
|
||||
let input = parse_macro_input!(input as ItemStruct);
|
||||
|
||||
let mut errors = Vec::new();
|
||||
let ret = lofty_tag::parse(args, input, &mut errors);
|
||||
|
||||
finish(ret, errors)
|
||||
}
|
||||
|
||||
fn finish(ret: proc_macro2::TokenStream, errors: Vec<syn::Error>) -> TokenStream {
|
||||
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
|
||||
|
||||
TokenStream::from(quote! {
|
||||
|
|
|
@ -1,46 +1,110 @@
|
|||
use crate::util::{self, bail};
|
||||
use crate::util::bail;
|
||||
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{DataStruct, DeriveInput, Meta, NestedMeta};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{AttributeArgs, ItemStruct, Lit, Meta, NestedMeta, Path};
|
||||
|
||||
enum SupportedFormat {
|
||||
Full(Path),
|
||||
ReadOnly(Path),
|
||||
}
|
||||
|
||||
pub(crate) fn parse(
|
||||
input: &DeriveInput,
|
||||
_data_struct: &DataStruct,
|
||||
attr: AttributeArgs,
|
||||
input: ItemStruct,
|
||||
errors: &mut Vec<syn::Error>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let mut supported_file_types_attr = None;
|
||||
for attr in &input.attrs {
|
||||
if let Some(list) = util::get_attr_list("lofty", attr) {
|
||||
if let Some(NestedMeta::Meta(Meta::List(ml))) = list.nested.first() {
|
||||
if ml
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.expect("path shouldn't be empty")
|
||||
.ident == "supported_formats"
|
||||
{
|
||||
supported_file_types_attr = Some(ml.clone());
|
||||
let mut desc = None;
|
||||
let mut supported_formats = Vec::new();
|
||||
let mut read_only_encountered = false;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
if let Lit::Str(s) = &mnv.lit {
|
||||
desc = Some(s.value());
|
||||
continue;
|
||||
} else {
|
||||
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;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
},
|
||||
_ => {
|
||||
bail!(
|
||||
errors,
|
||||
nested_meta.span(),
|
||||
"Unexpected input, check the format of the arguments"
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if supported_file_types_attr.is_none() {
|
||||
bail!(
|
||||
errors,
|
||||
input.ident.span(),
|
||||
"Tag has no #[lofty(supported_formats)] attribute"
|
||||
);
|
||||
}
|
||||
|
||||
let ident = &input.ident;
|
||||
let supported_file_types_attr = supported_file_types_attr.unwrap();
|
||||
let file_types_iter = supported_file_types_attr.nested.iter();
|
||||
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,
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[doc = #desc]
|
||||
#[doc = "\n"]
|
||||
#[doc = "## Supported file types\n\n"]
|
||||
#( #[doc = #supported_types_iter] )*
|
||||
#[doc = "\n"]
|
||||
#input
|
||||
|
||||
impl #ident {
|
||||
pub(crate) const SUPPORTED_FORMATS: &'static [lofty::FileType] = &[
|
||||
#( lofty::FileType:: #file_types_iter ),*
|
||||
#( lofty::FileType:: #flattened_file_types ),*
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use std::fs::{File, OpenOptions};
|
|||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use lofty_attr::LoftyTag;
|
||||
use lofty_attr::tag;
|
||||
|
||||
macro_rules! impl_accessor {
|
||||
($($name:ident => $($key:literal)|+;)+) => {
|
||||
|
@ -49,14 +49,6 @@ macro_rules! impl_accessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// An `APE` tag
|
||||
///
|
||||
/// ## Supported file types
|
||||
///
|
||||
/// * [`FileType::APE`](crate::FileType::APE)
|
||||
/// * [`FileType::MPEG`](crate::FileType::MPEG)
|
||||
/// * [`FileType::WavPack`](crate::FileType::WavPack)
|
||||
///
|
||||
/// ## Item storage
|
||||
///
|
||||
/// `APE` isn't a very strict format. An [`ApeItem`] only restricted by its name, meaning it can use
|
||||
|
@ -72,8 +64,8 @@ macro_rules! impl_accessor {
|
|||
///
|
||||
/// When converting pictures, any of type [`PictureType::Undefined`](crate::PictureType::Undefined) will be discarded.
|
||||
/// For items, see [`ApeItem::new`].
|
||||
#[derive(LoftyTag, Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[lofty(supported_formats(APE, MPEG, WavPack))]
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[tag(description = "An `APE` tag", supported_formats(APE, MPEG, WavPack))]
|
||||
pub struct ApeTag {
|
||||
/// Whether or not to mark the tag as read only
|
||||
pub read_only: bool,
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::fs::{File, OpenOptions};
|
|||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use lofty_attr::LoftyTag;
|
||||
use lofty_attr::tag;
|
||||
|
||||
macro_rules! impl_accessor {
|
||||
($($name:ident,)+) => {
|
||||
|
@ -30,8 +30,6 @@ macro_rules! impl_accessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// An ID3v1 tag
|
||||
///
|
||||
/// ID3v1 is a severely limited format, with each field
|
||||
/// being incredibly small in size. All fields have been
|
||||
/// commented with their maximum sizes and any other additional
|
||||
|
@ -40,12 +38,6 @@ macro_rules! impl_accessor {
|
|||
/// Attempting to write a field greater than the maximum size
|
||||
/// will **not** error, it will just be shrunk.
|
||||
///
|
||||
/// ## Supported file types
|
||||
///
|
||||
/// * [`FileType::APE`](crate::FileType::APE)
|
||||
/// * [`FileType::MP3`](crate::FileType::MPEG)
|
||||
/// * [`FileType::WavPack`](crate::FileType::WavPack)
|
||||
///
|
||||
/// ## Conversions
|
||||
///
|
||||
/// ### From `Tag`
|
||||
|
@ -54,8 +46,8 @@ macro_rules! impl_accessor {
|
|||
///
|
||||
/// * [`GENRES`] contains the string
|
||||
/// * The [`ItemValue`](crate::ItemValue) can be parsed into a `u8`
|
||||
#[derive(LoftyTag, Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[lofty(supported_formats(APE, MPEG, WavPack))]
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[tag(description = "An ID3v1 tag", supported_formats(APE, MPEG, WavPack))]
|
||||
pub struct ID3v1Tag {
|
||||
/// Track title, 30 bytes max
|
||||
pub title: Option<String>,
|
||||
|
|
|
@ -18,7 +18,7 @@ use std::fs::{File, OpenOptions};
|
|||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use lofty_attr::LoftyTag;
|
||||
use lofty_attr::tag;
|
||||
|
||||
macro_rules! impl_accessor {
|
||||
($($name:ident => $id:literal;)+) => {
|
||||
|
@ -61,15 +61,6 @@ macro_rules! impl_accessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// An `ID3v2` tag
|
||||
///
|
||||
/// ## Supported file types
|
||||
///
|
||||
/// * [`FileType::MPEG`](crate::FileType::MPEG)
|
||||
/// * [`FileType::WAV`](crate::FileType::WAV)
|
||||
/// * [`FileType::AIFF`](crate::FileType::AIFF)
|
||||
/// * [`FileType::APE`](crate::FileType::APE) **(READ ONLY)**
|
||||
///
|
||||
/// ## Conversions
|
||||
///
|
||||
/// ⚠ **Warnings** ⚠
|
||||
|
@ -98,8 +89,11 @@ macro_rules! impl_accessor {
|
|||
/// and [`SynchronizedText::parse`](crate::id3::v2::SynchronizedText::parse) respectively, and converted back to binary with
|
||||
/// [`GeneralEncapsulatedObject::as_bytes`](crate::id3::v2::GeneralEncapsulatedObject::as_bytes) and
|
||||
/// [`SynchronizedText::as_bytes`](crate::id3::v2::SynchronizedText::as_bytes) for writing.
|
||||
#[derive(LoftyTag, PartialEq, Eq, Debug, Clone)]
|
||||
#[lofty(supported_formats(APE, AIFF, FLAC, MPEG, WAV))]
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
#[tag(
|
||||
description = "An `ID3v2` tag",
|
||||
supported_formats(AIFF, MPEG, WAV, read_only(FLAC, APE))
|
||||
)]
|
||||
pub struct ID3v2Tag {
|
||||
flags: ID3v2TagFlags,
|
||||
pub(super) original_version: ID3v2Version,
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::io::{Read, Seek, SeekFrom, Write};
|
|||
use std::path::Path;
|
||||
|
||||
use byteorder::BigEndian;
|
||||
use lofty_attr::LoftyTag;
|
||||
use lofty_attr::tag;
|
||||
|
||||
/// Represents an AIFF `COMT` chunk
|
||||
///
|
||||
|
@ -34,12 +34,6 @@ pub struct Comment {
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
/// `AIFF` text chunks
|
||||
///
|
||||
/// ## Supported file types
|
||||
///
|
||||
/// * [`FileType::AIFF`](crate::FileType::AIFF)
|
||||
///
|
||||
/// ## Item storage
|
||||
///
|
||||
/// `AIFF` has a few chunks for storing basic metadata, all of
|
||||
|
@ -61,8 +55,8 @@ pub struct Comment {
|
|||
/// * [`ItemKey::Comment`](crate::ItemKey::Comment)
|
||||
///
|
||||
/// When converting [Comment]s, only the `text` field will be preserved.
|
||||
#[derive(LoftyTag, Default, Clone, Debug, PartialEq, Eq)]
|
||||
#[lofty(supported_formats(AIFF))]
|
||||
#[derive(Default, Clone, Debug, PartialEq, Eq)]
|
||||
#[tag(description = "`AIFF` text chunks", supported_formats(AIFF))]
|
||||
pub struct AIFFTextChunks {
|
||||
/// The name of the piece
|
||||
pub name: Option<String>,
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::fs::{File, OpenOptions};
|
|||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use lofty_attr::LoftyTag;
|
||||
use lofty_attr::tag;
|
||||
|
||||
macro_rules! impl_accessor {
|
||||
($($name:ident => $key:literal;)+) => {
|
||||
|
@ -32,12 +32,6 @@ macro_rules! impl_accessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// A RIFF INFO LIST
|
||||
///
|
||||
/// ## Supported file types
|
||||
///
|
||||
/// * [`FileType::WAV`](crate::FileType::WAV)
|
||||
///
|
||||
/// ## Conversions
|
||||
///
|
||||
/// ## From `Tag`
|
||||
|
@ -46,8 +40,8 @@ macro_rules! impl_accessor {
|
|||
///
|
||||
/// * The [`TagItem`] has a value other than [`ItemValue::Binary`](crate::ItemValue::Binary)
|
||||
/// * It has a key that is 4 bytes in length and within the ASCII range
|
||||
#[derive(LoftyTag, Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[lofty(supported_formats(WAV))]
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[tag(description = "A RIFF INFO LIST", supported_formats(WAV))]
|
||||
pub struct RIFFInfoList {
|
||||
/// A collection of chunk-value pairs
|
||||
pub(crate) items: Vec<(String, String)>,
|
||||
|
|
|
@ -18,7 +18,7 @@ use std::fs::{File, OpenOptions};
|
|||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use lofty_attr::LoftyTag;
|
||||
use lofty_attr::tag;
|
||||
|
||||
const ARTIST: AtomIdent = AtomIdent::Fourcc(*b"\xa9ART");
|
||||
const TITLE: AtomIdent = AtomIdent::Fourcc(*b"\xa9nam");
|
||||
|
@ -55,12 +55,6 @@ macro_rules! impl_accessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// An MP4 ilst atom
|
||||
///
|
||||
/// ## Supported file types
|
||||
///
|
||||
/// * [`FileType::MP4`](crate::FileType::MP4)
|
||||
///
|
||||
/// ## Pictures
|
||||
///
|
||||
/// Unlike other formats, ilst does not store a [`PictureType`]. All pictures will have
|
||||
|
@ -81,8 +75,8 @@ macro_rules! impl_accessor {
|
|||
/// well as pictures, will be preserved.
|
||||
///
|
||||
/// An attempt will be made to create the `TrackNumber/TrackTotal` (trkn) and `DiscNumber/DiscTotal` (disk) pairs.
|
||||
#[derive(LoftyTag, Default, PartialEq, Debug, Clone)]
|
||||
#[lofty(supported_formats(MP4))]
|
||||
#[derive(Default, PartialEq, Debug, Clone)]
|
||||
#[tag(description = "An MP4 ilst atom", supported_formats(MP4))]
|
||||
pub struct Ilst {
|
||||
pub(crate) atoms: Vec<Atom>,
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::fs::{File, OpenOptions};
|
|||
use std::io::{Cursor, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use lofty_attr::LoftyTag;
|
||||
use lofty_attr::tag;
|
||||
|
||||
macro_rules! impl_accessor {
|
||||
($($name:ident => $key:literal;)+) => {
|
||||
|
@ -34,16 +34,11 @@ macro_rules! impl_accessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Vorbis comments
|
||||
///
|
||||
/// ## Supported file types
|
||||
///
|
||||
/// * [`FileType::FLAC`](crate::FileType::FLAC)
|
||||
/// * [`FileType::Opus`](crate::FileType::Opus)
|
||||
/// * [`FileType::Speex`](crate::FileType::Speex)
|
||||
/// * [`FileType::Vorbis`](crate::FileType::Vorbis)
|
||||
#[derive(LoftyTag, Default, PartialEq, Eq, Debug, Clone)]
|
||||
#[lofty(supported_formats(FLAC, Opus, Speex, Vorbis))]
|
||||
#[derive(Default, PartialEq, Eq, Debug, Clone)]
|
||||
#[tag(
|
||||
description = "Vorbis comments",
|
||||
supported_formats(FLAC, Opus, Speex, Vorbis)
|
||||
)]
|
||||
pub struct VorbisComments {
|
||||
/// An identifier for the encoding software
|
||||
pub(crate) vendor: String,
|
||||
|
|
Loading…
Reference in a new issue