Create impl_tag attribute proc macro

This commit is contained in:
Serial 2021-05-18 22:21:23 -04:00
parent 2f9200a556
commit 0028651f70
5 changed files with 245 additions and 1 deletions

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
/target/
**/target/
# These are backup files generated by rustfmt
**/*.rs.bk

View file

@ -33,6 +33,10 @@ thiserror = "1.0.24"
base64 = "0.13.0"
byteorder = "1.4.3"
[dependencies.lofty_attr]
path = "./lofty-attr"
version = "0.1.0"
[features]
default = ["full"]
full = ["all_tags", "duration"]

46
lofty-attr/Cargo.lock generated Normal file
View file

@ -0,0 +1,46 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "lofty_attr"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"

17
lofty-attr/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "lofty_attr"
version = "0.1.0"
authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/Serial-ATA/lofty-rs"
edition = "2018"
[dependencies]
quote = "1.0.9"
[dependencies.syn]
version = "1.0.72"
features = ["full"]
[lib]
proc-macro = true

177
lofty-attr/src/lib.rs Normal file
View file

@ -0,0 +1,177 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, Error, ItemStruct, Meta, NestedMeta};
#[proc_macro_attribute]
#[allow(clippy::too_many_lines)]
pub fn impl_tag(args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
let args = parse_macro_input!(args as AttributeArgs);
if args.len() != 2 {
return Error::new(
input.ident.span(),
"impl_tag requires an inner tag and TagType",
)
.to_compile_error()
.into();
}
if let (NestedMeta::Meta(Meta::Path(inner)), NestedMeta::Meta(tag_type)) =
(args[0].clone(), args[1].clone())
{
if let Some(inner) = inner.get_ident() {
let input_ident = input.ident;
let expanded = quote! {
#[doc(hidden)]
pub struct #input_ident {
inner: #inner,
#[cfg(feature = "duration")]
duration: Option<std::time::Duration>
}
impl Default for #input_ident {
fn default() -> Self {
Self {
inner: #inner::default(),
#[cfg(feature = "duration")]
duration: None,
}
}
}
impl #input_ident {
/// Creates a new default tag
pub fn new() -> Self {
Self::default()
}
}
use std::any::Any;
impl ToAnyTag for #input_ident {
fn to_anytag(&self) -> AnyTag<'_> {
self.into()
}
}
impl ToAny for #input_ident {
fn to_any(&self) -> &dyn Any {
self
}
fn to_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl AudioTag for #input_ident {}
// From wrapper to inner (same type)
impl From<#input_ident> for #inner {
fn from(inp: #input_ident) -> Self {
inp.inner
}
}
// From inner to wrapper (same type)
impl From<#inner> for #input_ident {
fn from(inp: #inner) -> Self {
Self {
inner: inp,
#[cfg(feature = "duration")]
duration: None,
}
}
}
impl<'a> From<&'a #input_ident> for AnyTag<'a> {
fn from(inp: &'a #input_ident) -> Self {
Self {
title: inp.title(),
artists: inp.artists_vec(),
year: inp.year().map(|y| y as i32),
album: Album::new(
inp.album_title(),
inp.album_artists_vec(),
inp.album_covers(),
),
track_number: inp.track_number(),
total_tracks: inp.total_tracks(),
disc_number: inp.disc_number(),
total_discs: inp.total_discs(),
comments: None, // TODO
date: inp.date(),
}
}
}
impl<'a> From<AnyTag<'a>> for #input_ident {
fn from(inp: AnyTag<'a>) -> Self {
let mut tag = #input_ident::default();
if let Some(v) = inp.title() {
tag.set_title(v)
}
if let Some(v) = inp.artists_as_string() {
tag.set_artist(&v)
}
if let Some(v) = inp.year {
tag.set_year(v)
}
if let Some(v) = inp.album().title {
tag.set_album_title(v)
}
if let Some(v) = inp.album().artists {
tag.set_album_artist(&v.join("/"))
}
if let Some(v) = inp.track_number() {
tag.set_track_number(v)
}
if let Some(v) = inp.total_tracks() {
tag.set_total_tracks(v)
}
if let Some(v) = inp.disc_number() {
tag.set_disc_number(v)
}
if let Some(v) = inp.total_discs() {
tag.set_total_discs(v)
}
tag
}
}
// From dyn AudioTag to wrapper (any type)
impl From<Box<dyn AudioTag>> for #input_ident {
fn from(inp: Box<dyn AudioTag>) -> Self {
let mut inp = inp;
if let Some(t_refmut) = inp.to_any_mut().downcast_mut::<#input_ident>() {
let t = std::mem::replace(t_refmut, #input_ident::new()); // TODO: can we avoid creating the dummy tag?
t
} else {
let mut t = inp.to_dyn_tag(#tag_type);
let t_refmut = t.to_any_mut().downcast_mut::<#input_ident>().unwrap();
let t = std::mem::replace(t_refmut, #input_ident::new());
t
}
}
}
// From dyn AudioTag to inner (any type)
impl From<Box<dyn AudioTag>> for #inner {
fn from(inp: Box<dyn AudioTag>) -> Self {
let t: #input_ident = inp.into();
t.into()
}
}
};
return TokenStream::from(expanded);
}
}
Error::new(input.ident.span(), "impl_tag provided invalid arguments")
.to_compile_error()
.into()
}