lofty_attr: Generate internal write modules

This commit is contained in:
Serial 2022-09-24 05:25:42 -04:00
parent 364b9a741c
commit 3b58f16996
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
10 changed files with 165 additions and 145 deletions

121
lofty_attr/src/internal.rs Normal file
View file

@ -0,0 +1,121 @@
// Items that only pertain to internal usage of lofty_attr
use crate::FieldContents;
use std::collections::HashMap;
use quote::quote;
pub(crate) fn opt_internal_file_type(
struct_name: String,
) -> Option<(proc_macro2::TokenStream, bool)> {
const LOFTY_FILE_TYPES: [&str; 10] = [
"AIFF", "APE", "FLAC", "MPEG", "MP4", "Opus", "Vorbis", "Speex", "WAV", "WavPack",
];
const ID3V2_STRIPPABLE: [&str; 1] = ["APE"];
let stripped = struct_name.strip_suffix("File");
if let Some(prefix) = stripped {
if let Some(pos) = LOFTY_FILE_TYPES
.iter()
.position(|p| p.eq_ignore_ascii_case(prefix))
{
let file_ty = LOFTY_FILE_TYPES[pos];
let tt = file_ty.parse::<proc_macro2::TokenStream>().unwrap();
return Some((tt, ID3V2_STRIPPABLE.contains(&file_ty)));
}
}
None
}
pub(crate) fn init_write_lookup(
id3v2_strippable: bool,
) -> HashMap<&'static str, proc_macro2::TokenStream> {
let mut map = HashMap::new();
macro_rules! insert {
($map:ident, $key:path, $val:block) => {
$map.insert(stringify!($key), quote! { $val })
};
}
insert!(map, APE, {
crate::ape::tag::ApeTagRef {
read_only: false,
items: crate::ape::tag::tagitems_into_ape(tag.items()),
}
.write_to(data)
});
insert!(map, ID3v1, {
Into::<crate::id3::v1::tag::Id3v1TagRef<'_>>::into(tag).write_to(data)
});
if id3v2_strippable {
insert!(map, ID3v2, {
crate::id3::v2::tag::Id3v2TagRef::empty().write_to(data)
});
} else {
insert!(map, ID3v2, {
crate::id3::v2::tag::Id3v2TagRef {
flags: crate::id3::v2::ID3v2TagFlags::default(),
frames: crate::id3::v2::tag::tag_frames(tag),
}
.write_to(data)
});
}
insert!(map, RIFFInfo, {
crate::iff::wav::tag::RIFFInfoListRef::new(crate::iff::wav::tag::tagitems_into_riff(
tag.items(),
))
.write_to(data)
});
insert!(map, AIFFText, {
crate::iff::aiff::tag::AiffTextChunksRef {
name: tag.get_string(&crate::tag::item::ItemKey::TrackTitle),
author: tag.get_string(&crate::tag::item::ItemKey::TrackArtist),
copyright: tag.get_string(&crate::tag::item::ItemKey::CopyrightMessage),
annotations: Some(tag.get_strings(&crate::tag::item::ItemKey::Comment)),
comments: None,
}
.write_to(data)
});
map
}
pub(crate) fn write_module(
fields: &[FieldContents],
lookup: HashMap<&'static str, proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let applicable_formats = fields.iter().map(|f| {
let tag_ty =
syn::parse_str::<syn::Path>(&format!("crate::tag::TagType::{}", &f.tag_type)).unwrap();
let features = f.cfg_features.iter();
let block = lookup.get(&*tag_ty.segments[3].ident.to_string()).unwrap();
quote! {
#( #features )*
#tag_ty => #block,
}
});
quote! {
pub(crate) mod write {
#[allow(unused_variables)]
pub(crate) fn write_to(data: &mut std::fs::File, tag: &crate::tag::Tag) -> crate::error::Result<()> {
match tag.tag_type() {
#( #applicable_formats )*
_ => crate::macros::err!(UnsupportedTag),
}
}
}
}
}

View file

@ -1,3 +1,4 @@
mod internal;
mod util;
use proc_macro::TokenStream;
@ -6,10 +7,6 @@ use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
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",
];
/// 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.
@ -68,9 +65,30 @@ fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::Token
};
let struct_name = input.ident.clone();
let is_internal = input
.attrs
.iter()
.any(|attr| util::has_path_attr(attr, "internal_write_module_do_not_use_anywhere_else"));
let file_type = match opt_file_type(struct_name.to_string()) {
Some(ft) => ft,
// TODO: This is not readable in the slightest
let opt_file_type = internal::opt_internal_file_type(struct_name.to_string());
if opt_file_type.is_none() && is_internal {
// TODO: This is the best check we can do for now I think?
// Definitely needs some work when a better solution comes out.
bail!(
errors,
input.ident.span(),
"Attempted to use an internal attribute externally"
);
}
let mut id3v2_strippable = false;
let file_type = match opt_file_type {
Some((ft, id3v2_strip)) => {
id3v2_strippable = id3v2_strip;
ft
},
_ => match util::get_attr("file_type", &input.attrs) {
Some(rfn) => rfn,
_ => {
@ -168,7 +186,7 @@ fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::Token
let getters = get_getters(&tag_fields, &struct_name);
quote! {
let mut ret = quote! {
#assert_properties_impl
#( #assert_tag_impl_into )*
@ -191,7 +209,21 @@ fn parse(input: DeriveInput, errors: &mut Vec<syn::Error>) -> proc_macro2::Token
}
#( #getters )*
};
// Create `write` module if internal
if is_internal {
let lookup = internal::init_write_lookup(id3v2_strippable);
let write_mod = internal::write_module(&tag_fields, lookup);
ret = quote! {
#ret
#write_mod
}
}
ret
}
struct FieldContents {
@ -252,24 +284,6 @@ fn get_fields<'a>(
Some((tag_fields, properties_field))
}
fn opt_file_type(struct_name: String) -> Option<proc_macro2::TokenStream> {
let stripped = struct_name.strip_suffix("File");
if let Some(prefix) = stripped {
if let Some(pos) = LOFTY_FILE_TYPES
.iter()
.position(|p| p.eq_ignore_ascii_case(prefix))
{
return Some(
LOFTY_FILE_TYPES[pos]
.parse::<proc_macro2::TokenStream>()
.unwrap(),
);
}
}
None
}
fn should_impl_audiofile(attrs: &[Attribute]) -> bool {
for attr in attrs {
if util::has_path_attr(attr, "no_audiofile_impl") {

View file

@ -9,7 +9,6 @@ pub(crate) mod constants;
pub(crate) mod header;
mod properties;
mod read;
pub(crate) mod write;
#[cfg(feature = "id3v1")]
use crate::id3::v1::tag::ID3v1Tag;
@ -35,6 +34,7 @@ pub use properties::ApeProperties;
/// An APE file
#[derive(LoftyFile)]
#[lofty(read_fn = "read::read_from")]
#[lofty(internal_write_module_do_not_use_anywhere_else)]
pub struct ApeFile {
/// An ID3v1 tag
#[cfg(feature = "id3v1")]

View file

@ -1,30 +0,0 @@
#[cfg(feature = "ape")]
use crate::ape;
use crate::error::Result;
#[cfg(feature = "id3v1")]
use crate::id3::v1;
#[cfg(feature = "id3v2")]
use crate::id3::v2;
use crate::macros::err;
#[allow(unused_imports)]
use crate::tag::{Tag, TagType};
use std::fs::File;
#[allow(unused_variables)]
pub(crate) fn write_to(data: &mut File, tag: &Tag) -> Result<()> {
match tag.tag_type() {
#[cfg(feature = "ape")]
TagType::APE => ape::tag::ApeTagRef {
read_only: false,
items: ape::tag::tagitems_into_ape(tag.items()),
}
.write_to(data),
// This tag can *only* be removed in this format
#[cfg(feature = "id3v2")]
TagType::ID3v2 => v2::tag::Id3v2TagRef::empty().write_to(data),
#[cfg(feature = "id3v1")]
TagType::ID3v1 => Into::<v1::tag::Id3v1TagRef<'_>>::into(tag).write_to(data),
_ => err!(UnsupportedTag),
}
}

View file

@ -1,6 +1,5 @@
mod properties;
mod read;
pub(crate) mod write;
#[cfg(feature = "id3v2")]
use crate::id3::v2::tag::ID3v2Tag;
@ -18,6 +17,7 @@ cfg_if::cfg_if! {
/// An AIFF file
#[derive(LoftyFile)]
#[lofty(read_fn = "read::read_from")]
#[lofty(internal_write_module_do_not_use_anywhere_else)]
pub struct AiffFile {
/// Any text chunks included in the file
#[cfg(feature = "aiff_text_chunks")]

View file

@ -1,34 +0,0 @@
use crate::error::Result;
#[cfg(feature = "id3v2")]
use crate::id3::v2;
use crate::macros::err;
#[allow(unused_imports)]
use crate::tag::{Tag, TagType};
use std::fs::File;
#[allow(unused_variables)]
pub(crate) fn write_to(data: &mut File, tag: &Tag) -> Result<()> {
match tag.tag_type() {
#[cfg(feature = "aiff_text_chunks")]
TagType::AIFFText => {
use crate::tag::item::ItemKey;
super::tag::AiffTextChunksRef {
name: tag.get_string(&ItemKey::TrackTitle),
author: tag.get_string(&ItemKey::TrackArtist),
copyright: tag.get_string(&ItemKey::CopyrightMessage),
annotations: Some(tag.get_strings(&ItemKey::Comment)),
comments: None,
}
}
.write_to(data),
#[cfg(feature = "id3v2")]
TagType::ID3v2 => v2::tag::Id3v2TagRef {
flags: v2::ID3v2TagFlags::default(),
frames: v2::tag::tag_frames(tag),
}
.write_to(data),
_ => err!(UnsupportedTag),
}
}

View file

@ -1,6 +1,5 @@
mod properties;
mod read;
pub(crate) mod write;
#[cfg(feature = "id3v2")]
use crate::id3::v2::tag::ID3v2Tag;
@ -20,6 +19,7 @@ pub use crate::iff::wav::properties::{WavFormat, WavProperties};
/// A WAV file
#[derive(LoftyFile)]
#[lofty(read_fn = "read::read_from")]
#[lofty(internal_write_module_do_not_use_anywhere_else)]
pub struct WavFile {
/// A RIFF INFO LIST
#[cfg(feature = "riff_info_list")]

View file

@ -1,26 +0,0 @@
use crate::error::Result;
#[cfg(feature = "id3v2")]
use crate::id3::v2;
use crate::macros::err;
#[allow(unused_imports)]
use crate::tag::{Tag, TagType};
use std::fs::File;
#[allow(unused_variables)]
pub(crate) fn write_to(data: &mut File, tag: &Tag) -> Result<()> {
match tag.tag_type() {
#[cfg(feature = "riff_info_list")]
TagType::RIFFInfo => {
super::tag::RIFFInfoListRef::new(super::tag::tagitems_into_riff(tag.items()))
.write_to(data)
},
#[cfg(feature = "id3v2")]
TagType::ID3v2 => v2::tag::Id3v2TagRef {
flags: v2::ID3v2TagFlags::default(),
frames: v2::tag::tag_frames(tag),
}
.write_to(data),
_ => err!(UnsupportedTag),
}
}

View file

@ -1,7 +1,6 @@
//! WavPack specific items
mod properties;
mod read;
pub(crate) mod write;
#[cfg(feature = "ape")]
use crate::ape::tag::ApeTag;
@ -14,8 +13,9 @@ use lofty_attr::LoftyFile;
pub use properties::WavPackProperties;
/// A WavPack file
#[derive(Default, LoftyFile)]
#[derive(LoftyFile, Default)]
#[lofty(read_fn = "read::read_from")]
#[lofty(internal_write_module_do_not_use_anywhere_else)]
pub struct WavPackFile {
/// An ID3v1 tag
#[cfg(feature = "id3v1")]

View file

@ -1,25 +0,0 @@
#[cfg(feature = "ape")]
use crate::ape;
use crate::error::Result;
#[cfg(feature = "id3v1")]
use crate::id3::v1;
use crate::macros::err;
#[allow(unused_imports)]
use crate::tag::{Tag, TagType};
use std::fs::File;
#[allow(unused_variables)]
pub(crate) fn write_to(data: &mut File, tag: &Tag) -> Result<()> {
match tag.tag_type() {
#[cfg(feature = "ape")]
TagType::APE => ape::tag::ApeTagRef {
read_only: false,
items: ape::tag::tagitems_into_ape(tag.items()),
}
.write_to(data),
#[cfg(feature = "id3v1")]
TagType::ID3v1 => Into::<v1::tag::Id3v1TagRef<'_>>::into(tag).write_to(data),
_ => err!(UnsupportedTag),
}
}