Finalize Accessor and TagExt documentation

This commit is contained in:
Serial 2022-02-19 09:39:09 -05:00
parent 74b4374172
commit e66ab4587d
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
20 changed files with 197 additions and 123 deletions

View file

@ -4,7 +4,7 @@ use lofty::id3::v2::Id3v2Tag;
use lofty::iff::{AiffTextChunks, RiffInfoList};
use lofty::mp4::Ilst;
use lofty::ogg::VorbisComments;
use lofty::{Accessor, TagIO};
use lofty::{Accessor, TagExt};
use criterion::{criterion_group, criterion_main, Criterion};

View file

@ -1,4 +1,4 @@
use lofty::{Accessor, Probe, Tag, TagIO};
use lofty::{Accessor, Probe, Tag, TagExt};
use structopt::StructOpt;

View file

@ -1,7 +1,8 @@
use crate::ape::tag::item::{ApeItem, ApeItemRef};
use crate::error::{LoftyError, Result};
use crate::tag_traits::{Accessor, TagExt};
use crate::types::item::{ItemKey, ItemValue, TagItem};
use crate::types::tag::{Accessor, Tag, TagIO, TagType};
use crate::types::tag::{Tag, TagType};
use std::convert::TryInto;
use std::fs::{File, OpenOptions};
@ -115,7 +116,7 @@ impl ApeTag {
}
}
impl TagIO for ApeTag {
impl TagExt for ApeTag {
type Err = LoftyError;
fn is_empty(&self) -> bool {
@ -278,7 +279,7 @@ pub(crate) fn tagitems_into_ape(items: &[TagItem]) -> impl Iterator<Item = ApeIt
mod tests {
use crate::ape::header::read_ape_header;
use crate::ape::{ApeItem, ApeTag};
use crate::{ItemValue, Tag, TagIO, TagType};
use crate::{ItemValue, Tag, TagExt, TagType};
use std::io::Cursor;

View file

@ -1,7 +1,8 @@
use crate::error::{LoftyError, Result};
use crate::id3::v1::constants::GENRES;
use crate::tag_traits::{Accessor, TagExt};
use crate::types::item::{ItemKey, ItemValue, TagItem};
use crate::types::tag::{Accessor, Tag, TagIO, TagType};
use crate::types::tag::{Tag, TagType};
use std::fs::{File, OpenOptions};
use std::io::Write;
@ -110,7 +111,7 @@ impl Accessor for Id3v1Tag {
}
}
impl TagIO for Id3v1Tag {
impl TagExt for Id3v1Tag {
type Err = LoftyError;
fn is_empty(&self) -> bool {
@ -278,7 +279,7 @@ impl<'a> Id3v1TagRef<'a> {
#[cfg(test)]
mod tests {
use crate::id3::v1::Id3v1Tag;
use crate::{Tag, TagIO, TagType};
use crate::{Tag, TagExt, TagType};
#[test]
fn parse_id3v1() {

View file

@ -6,9 +6,10 @@ use super::util::text_utils::TextEncoding;
use super::Id3v2Version;
use crate::error::{LoftyError, Result};
use crate::id3::v2::frame::FrameRef;
use crate::tag_traits::{Accessor, TagExt};
use crate::types::item::{ItemKey, ItemValue, TagItem};
use crate::types::picture::{Picture, PictureType};
use crate::types::tag::{Accessor, Tag, TagIO, TagType};
use crate::types::tag::{Tag, TagType};
use std::convert::TryInto;
use std::fs::{File, OpenOptions};
@ -269,7 +270,7 @@ impl Id3v2Tag {
}
}
impl TagIO for Id3v2Tag {
impl TagExt for Id3v2Tag {
type Err = LoftyError;
fn is_empty(&self) -> bool {
@ -448,7 +449,7 @@ mod tests {
LanguageFrame, TextEncoding,
};
use crate::tag_utils::test_utils::read_path;
use crate::{MimeType, Picture, PictureType, Tag, TagIO, TagType};
use crate::{MimeType, Picture, PictureType, Tag, TagExt, TagType};
fn read_tag(path: &str) -> Id3v2Tag {
let tag_bytes = crate::tag_utils::test_utils::read_path(path);

View file

@ -1,7 +1,8 @@
use crate::error::{ErrorKind, LoftyError, Result};
use crate::iff::chunk::Chunks;
use crate::tag_traits::{Accessor, TagExt};
use crate::types::item::{ItemKey, ItemValue, TagItem};
use crate::types::tag::{Accessor, Tag, TagIO, TagType};
use crate::types::tag::{Tag, TagType};
use std::convert::TryFrom;
use std::fs::{File, OpenOptions};
@ -113,7 +114,7 @@ impl AiffTextChunks {
}
}
impl TagIO for AiffTextChunks {
impl TagExt for AiffTextChunks {
type Err = LoftyError;
fn is_empty(&self) -> bool {
@ -405,7 +406,7 @@ where
#[cfg(test)]
mod tests {
use crate::iff::{AiffTextChunks, Comment};
use crate::{ItemKey, ItemValue, Tag, TagIO, TagItem, TagType};
use crate::{ItemKey, ItemValue, Tag, TagExt, TagItem, TagType};
use std::io::Cursor;

View file

@ -2,8 +2,9 @@ pub(super) mod read;
mod write;
use crate::error::{LoftyError, Result};
use crate::tag_traits::{Accessor, TagExt};
use crate::types::item::{ItemKey, ItemValue, TagItem};
use crate::types::tag::{Accessor, Tag, TagIO, TagType};
use crate::types::tag::{Tag, TagType};
use std::fs::{File, OpenOptions};
use std::io::Write;
@ -98,7 +99,7 @@ impl RiffInfoList {
}
}
impl TagIO for RiffInfoList {
impl TagExt for RiffInfoList {
type Err = LoftyError;
fn is_empty(&self) -> bool {
@ -217,7 +218,7 @@ pub(crate) fn tagitems_into_riff(items: &[TagItem]) -> impl Iterator<Item = (&st
#[cfg(test)]
mod tests {
use crate::iff::RiffInfoList;
use crate::{Tag, TagIO, TagType};
use crate::{Tag, TagExt, TagType};
use crate::iff::chunk::Chunks;
use byteorder::LittleEndian;

View file

@ -169,6 +169,7 @@ pub mod mp3;
pub mod mp4;
pub mod ogg;
mod probe;
mod tag_traits;
pub(crate) mod tag_utils;
mod types;
@ -180,7 +181,9 @@ pub use crate::types::file::{AudioFile, FileType, TaggedFile};
pub use crate::types::item::{ItemKey, ItemValue, TagItem};
pub use crate::types::picture::{MimeType, Picture, PictureType};
pub use crate::types::properties::FileProperties;
pub use crate::types::tag::{Accessor, Tag, TagIO, TagType};
pub use crate::types::tag::{Tag, TagType};
pub use crate::tag_traits::{Accessor, TagExt};
#[cfg(feature = "vorbis_comments")]
pub use crate::types::picture::PictureInformation;

View file

@ -5,9 +5,10 @@ pub(crate) mod write;
use super::AtomIdent;
use crate::error::{LoftyError, Result};
use crate::tag_traits::{Accessor, TagExt};
use crate::types::item::{ItemKey, ItemValue, TagItem};
use crate::types::picture::{Picture, PictureType};
use crate::types::tag::{Accessor, Tag, TagIO, TagType};
use crate::types::tag::{Tag, TagType};
use atom::{AdvisoryRating, Atom, AtomData, AtomDataRef, AtomIdentRef, AtomRef};
use std::convert::TryInto;
@ -205,7 +206,7 @@ impl Ilst {
}
}
impl TagIO for Ilst {
impl TagExt for Ilst {
type Err = LoftyError;
fn is_empty(&self) -> bool {
@ -437,7 +438,7 @@ fn item_key_to_ident(key: &ItemKey) -> Option<AtomIdentRef<'_>> {
#[cfg(test)]
mod tests {
use crate::mp4::{AdvisoryRating, Atom, AtomData, AtomIdent, Ilst};
use crate::{ItemKey, Tag, TagIO, TagType};
use crate::{ItemKey, Tag, TagExt, TagType};
fn read_ilst(path: &str) -> Ilst {
let tag = crate::tag_utils::test_utils::read_path(path);

View file

@ -1,10 +1,11 @@
use crate::error::{ErrorKind, LoftyError, Result};
use crate::ogg::write::OGGFormat;
use crate::probe::Probe;
use crate::tag_traits::{Accessor, TagExt};
use crate::types::file::FileType;
use crate::types::item::{ItemKey, ItemValue, TagItem};
use crate::types::picture::{Picture, PictureInformation, PictureType};
use crate::types::tag::{Accessor, Tag, TagIO, TagType};
use crate::types::tag::{Tag, TagType};
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Write};
@ -133,7 +134,7 @@ impl VorbisComments {
}
}
impl TagIO for VorbisComments {
impl TagExt for VorbisComments {
type Err = LoftyError;
fn is_empty(&self) -> bool {
@ -331,7 +332,7 @@ pub(crate) fn create_vorbis_comments_ref(
#[cfg(test)]
mod tests {
use crate::ogg::VorbisComments;
use crate::{Tag, TagIO, TagType};
use crate::{Tag, TagExt, TagType};
use std::io::Read;

131
src/tag_traits.rs Normal file
View file

@ -0,0 +1,131 @@
macro_rules! accessor_trait {
($($name:ident),+) => {
/// Provides accessors for common items
///
/// This attempts to only provide methods for items that all tags have in common,
/// but there may be exceptions.
pub trait Accessor {
paste::paste! {
$(
#[doc = "Returns the " $name]
/// # Example
///
/// ```rust
/// use lofty::{Tag, Accessor};
/// # let tag_type = lofty::TagType::Id3v2;
///
/// let mut tag = Tag::new(tag_type);
///
#[doc = "assert_eq!(tag." $name "(), None);"]
/// ```
fn $name(&self) -> Option<&str> { None }
#[doc = "Sets the " $name]
/// # Example
///
/// ```rust
/// use lofty::{Tag, Accessor};
/// # let tag_type = lofty::TagType::Id3v2;
///
#[doc = "let mut tag = Tag::new(tag_type);\ntag.set_" $name "(String::from(\"Foo " $name "\"));"]
///
#[doc = "assert_eq!(tag." $name "(), Some(\"Foo " $name "\"));"]
/// ```
fn [<set_ $name>](&mut self, _value: String) {}
#[doc = "Removes the " $name]
///
/// # Example
///
/// ```rust
/// use lofty::{Tag, Accessor};
/// # let tag_type = lofty::TagType::Id3v2;
///
#[doc = "let mut tag = Tag::new(tag_type);\ntag.set_" $name "(String::from(\"Foo " $name "\"));"]
///
#[doc = "assert_eq!(tag." $name "(), Some(\"Foo " $name "\"));"]
///
#[doc = "tag.remove_" $name "();"]
///
#[doc = "assert_eq!(tag." $name "(), None);"]
/// ```
fn [<remove_ $name>](&mut self) {}
)+
}
}
};
}
accessor_trait! {
artist, title,
album, genre
}
use crate::types::tag::Tag;
use std::fs::File;
use std::path::Path;
/// A set of common methods between tags
///
/// This provides a set of methods to make interaction with all tags a similar
/// experience.
///
/// This can be implemented downstream to provide a familiar interface for custom tags.
pub trait TagExt: Accessor + Into<Tag> + Sized {
/// The associated error which can be returned from IO operations
type Err;
/// Whether the tag has any items
///
/// # Example
///
/// ```rust
/// use lofty::{Tag, TagExt, Accessor};
/// # let tag_type = lofty::TagType::Id3v2;
///
/// let mut tag = Tag::new(tag_type);
/// assert!(tag.is_empty());
///
/// tag.set_artist(String::from("Foo artist"));
/// assert!(!tag.is_empty());
/// ```
fn is_empty(&self) -> bool;
/// Save the tag to a path
///
/// # Errors
///
/// * Path doesn't exist
/// * Path is not writable
/// * See [`TagExt::save_to`]
fn save_to_path<P: AsRef<Path>>(&self, path: P) -> std::result::Result<(), Self::Err>;
/// Save the tag to a [`File`]
///
/// # Errors
///
/// * The file format could not be determined
/// * Attempting to write a tag to a format that does not support it.
fn save_to(&self, file: &mut File) -> std::result::Result<(), Self::Err>;
#[allow(clippy::missing_errors_doc)]
/// Dump the tag to a writer
///
/// This will only write the tag, it will not produce a usable file.
fn dump_to<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), Self::Err>;
/// Remove a tag from a [`Path`]
///
/// # Errors
///
/// See [`TagExt::remove_from`]
fn remove_from_path<P: AsRef<Path>>(&self, path: P) -> std::result::Result<(), Self::Err>;
/// Remove a tag from a [`File`]
///
/// # Errors
///
/// * It is unable to guess the file format
/// * The format doesn't support the tag
/// * It is unable to write to the file
fn remove_from(&self, file: &mut File) -> std::result::Result<(), Self::Err>;
}

View file

@ -1,24 +1,23 @@
#[cfg(feature = "ape")]
use crate::ape::tag::ape_tag::ApeTagRef;
use crate::error::{ErrorKind, LoftyError, Result};
use crate::types::file::FileType;
use crate::types::item::ItemKey;
use crate::types::tag::{Tag, TagType};
use crate::{ape, iff, mp3, mp4, ogg};
#[cfg(feature = "id3v1")]
use crate::id3::v1::tag::Id3v1TagRef;
#[cfg(feature = "id3v2")]
use crate::id3::v2::{self, tag::Id3v2TagRef, Id3v2TagFlags};
#[cfg(feature = "ape")]
use ape::tag::ape_tag::ApeTagRef;
#[cfg(feature = "aiff_text_chunks")]
use crate::iff::aiff::tag::AiffTextChunksRef;
use iff::aiff::tag::AiffTextChunksRef;
#[cfg(feature = "riff_info_list")]
use crate::iff::wav::tag::RiffInfoListRef;
use iff::wav::tag::RiffInfoListRef;
#[cfg(feature = "mp4_ilst")]
use crate::mp4::ilst::IlstRef;
use mp4::ilst::IlstRef;
#[cfg(feature = "vorbis_comments")]
use crate::ogg::tag::{create_vorbis_comments_ref, VorbisCommentsRef};
use crate::{ape, iff, mp3, mp4, ogg};
use crate::types::file::FileType;
use crate::types::item::ItemKey;
use crate::types::tag::{Tag, TagType};
use ogg::tag::{create_vorbis_comments_ref, VorbisCommentsRef};
use std::fs::File;
use std::io::Write;

View file

@ -1,6 +1,7 @@
use super::properties::FileProperties;
use super::tag::{Tag, TagIO, TagType};
use super::tag::{Tag, TagType};
use crate::error::Result;
use crate::tag_traits::TagExt;
use std::convert::TryInto;
use std::ffi::OsStr;

View file

@ -2,34 +2,12 @@ use super::item::{ItemKey, ItemValue, TagItem};
use super::picture::{Picture, PictureType};
use crate::error::{ErrorKind, LoftyError, Result};
use crate::probe::Probe;
use crate::tag_traits::{Accessor, TagExt};
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
macro_rules! accessor_trait {
($($name:ident),+) => {
/// Provides accessors for common items
pub trait Accessor {
paste::paste! {
$(
#[doc = "Gets the " $name]
fn $name(&self) -> Option<&str> { None }
#[doc = "Sets the " $name]
fn [<set_ $name>](&mut self, _value: String) {}
#[doc = "Removes the " $name]
fn [<remove_ $name>](&mut self) {}
)+
}
}
};
}
accessor_trait! {
artist, title,
album, genre
}
macro_rules! impl_accessor {
($($item_key:ident => $name:tt),+) => {
paste::paste! {
@ -56,54 +34,6 @@ macro_rules! impl_accessor {
}
}
// TODO
#[allow(missing_docs)]
pub trait TagIO: Accessor + Sized {
type Err;
/// Whether the tag has any items
fn is_empty(&self) -> bool;
/// Save the tag to a path
///
/// # Errors
///
/// * Path doesn't exist
/// * Path is not writable
/// * See [`TagIO::save_to`]
fn save_to_path<P: AsRef<Path>>(&self, path: P) -> std::result::Result<(), Self::Err>;
/// Save the tag to a [`File`]
///
/// # Errors
///
/// * The file format could not be determined
/// * Attempting to write a tag to a format that does not support it.
fn save_to(&self, file: &mut File) -> std::result::Result<(), Self::Err>;
#[allow(clippy::missing_errors_doc)]
/// Dump the tag to a writer
///
/// This will only write the tag, it will not produce a usable file.
fn dump_to<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), Self::Err>;
/// Remove a tag from a [`Path`]
///
/// # Errors
///
/// See [`TagIO::remove_from`]
fn remove_from_path<P: AsRef<Path>>(&self, path: P) -> std::result::Result<(), Self::Err>;
/// Remove a tag from a [`File`]
///
/// # Errors
///
/// * It is unable to guess the file format
/// * The format doesn't support the tag
/// * It is unable to write to the file
fn remove_from(&self, file: &mut File) -> std::result::Result<(), Self::Err>;
}
#[derive(Clone)]
/// Represents a parsed tag
///
@ -123,8 +53,10 @@ pub trait TagIO: Accessor + Sized {
/// Accessing common items
///
/// ```rust
/// # use lofty::{Tag, TagType, Accessor};
/// # let tag = Tag::new(TagType::Id3v2);
/// use lofty::{Tag, TagType, Accessor};
///
/// let tag = Tag::new(TagType::Id3v2);
///
/// // There are multiple quick getter methods for common items
///
/// let title = tag.title();
@ -136,10 +68,11 @@ pub trait TagIO: Accessor + Sized {
/// Getting an item of a known type
///
/// ```rust
/// # use lofty::{Tag, TagType};
/// # let tag = Tag::new(TagType::Id3v2);
/// use lofty::{Tag, TagType};
/// use lofty::ItemKey;
///
/// let tag = Tag::new(TagType::Id3v2);
///
/// // If the type of an item is known, there are getter methods
/// // to prevent having to match against the value
///
@ -150,8 +83,8 @@ pub trait TagIO: Accessor + Sized {
/// Converting between formats
///
/// ```rust
/// use lofty::id3::v2::Id3v2Tag;
/// use lofty::{Tag, TagType};
/// use lofty::id3::v2::Id3v2Tag;
///
/// // Converting between formats is as simple as an `into` call.
/// // However, such conversions can potentially be *very* lossy.
@ -186,8 +119,8 @@ impl Tag {
pub fn new(tag_type: TagType) -> Self {
Self {
tag_type,
pictures: vec![],
items: vec![],
pictures: Vec::new(),
items: Vec::new(),
}
}
@ -400,11 +333,11 @@ impl Tag {
}
}
impl TagIO for Tag {
impl TagExt for Tag {
type Err = LoftyError;
fn is_empty(&self) -> bool {
self.pictures.is_empty() && self.pictures.is_empty()
self.items.is_empty() && self.pictures.is_empty()
}
/// Save the `Tag` to a path

View file

@ -1,5 +1,5 @@
use crate::{set_artist, temp_file, verify_artist};
use lofty::{FileType, ItemKey, ItemValue, TagIO, TagItem, TagType};
use lofty::{FileType, ItemKey, ItemValue, TagExt, TagItem, TagType};
use std::io::{Seek, SeekFrom, Write};
#[test]

View file

@ -1,5 +1,5 @@
use crate::{set_artist, temp_file, verify_artist};
use lofty::{FileType, ItemKey, ItemValue, TagIO, TagItem, TagType};
use lofty::{FileType, ItemKey, ItemValue, TagExt, TagItem, TagType};
use std::io::{Seek, SeekFrom, Write};
#[test]

View file

@ -1,5 +1,5 @@
use crate::{set_artist, temp_file, verify_artist};
use lofty::{FileType, ItemKey, ItemValue, TagIO, TagItem, TagType};
use lofty::{FileType, ItemKey, ItemValue, TagExt, TagItem, TagType};
use std::io::{Seek, SeekFrom, Write};
#[test]

View file

@ -1,5 +1,5 @@
use crate::{set_artist, temp_file, verify_artist};
use lofty::{Accessor, FileType, ItemKey, ItemValue, TagIO, TagItem, TagType};
use lofty::{Accessor, FileType, ItemKey, ItemValue, TagExt, TagItem, TagType};
use std::io::{Seek, SeekFrom, Write};
#[test]

View file

@ -1,5 +1,5 @@
use crate::{set_artist, temp_file, verify_artist};
use lofty::{FileType, ItemKey, ItemValue, TagIO, TagItem, TagType};
use lofty::{FileType, ItemKey, ItemValue, TagExt, TagItem, TagType};
use std::io::{Seek, SeekFrom, Write};
// The tests for OGG Opus/Vorbis are nearly identical

View file

@ -1,5 +1,5 @@
use crate::{set_artist, temp_file, verify_artist};
use lofty::{FileType, ItemKey, ItemValue, TagIO, TagItem, TagType};
use lofty::{FileType, ItemKey, ItemValue, TagExt, TagItem, TagType};
use std::io::{Seek, SeekFrom, Write};
#[test]