use one_or_many::OneOrMany;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum MetadataConflictResolution {
#[default]
Overwrite,
Skip,
}
#[cfg(all(test, feature = "serde"))]
mod metadata_conflict_resolution {
use rstest::rstest;
use super::*;
#[test]
fn test_default() {
assert_eq!(
MetadataConflictResolution::default(),
MetadataConflictResolution::Overwrite
);
}
#[rstest]
#[case::lower(MetadataConflictResolution::Overwrite, "overwrite")]
#[case::lower(MetadataConflictResolution::Skip, "skip")]
fn test_deserialize<D, 'de>(#[case] expected: MetadataConflictResolution, #[case] input: D)
where
D: serde::de::IntoDeserializer<'de>,
{
let actual: MetadataConflictResolution =
MetadataConflictResolution::deserialize(input.into_deserializer()).unwrap();
assert_eq!(actual, expected);
}
}
#[inline]
#[must_use]
pub fn split_artist_name(
artist: &str,
artist_name_separator: &OneOrMany<String>,
exceptions: &OneOrMany<String>,
) -> OneOrMany<String> {
let mut artists = OneOrMany::None;
let mut left = 0;
let mut right = 0;
while right < artist.len() {
let rest_right = &artist[right..];
if let Some(sep) = artist_name_separator
.iter()
.find(|sep| rest_right.starts_with(*sep))
{
let rest_left = &artist[left..];
if let Some(exception) = exceptions
.iter()
.find(|exception| rest_left.starts_with(*exception))
{
let exception_len = exception.len();
let after_exception = &artist[left + exception_len..];
if after_exception.is_empty() {
break;
}
if let Some(sep) = artist_name_separator
.iter()
.find(|sep| after_exception.starts_with(*sep))
{
let new = artist[left..left + exception_len].trim().replace('\0', "");
if !new.is_empty() {
artists.push(new);
}
left += exception_len + sep.len();
right = left;
continue;
}
}
let new = artist[left..right].trim().replace('\0', "");
if !new.is_empty() {
artists.push(new);
}
right += sep.len();
left = right;
} else {
right += 1;
while !artist.is_char_boundary(right) && right < artist.len() {
right += 1;
}
}
}
if left < artist.len() {
let new = artist[left..].trim().replace('\0', "");
if !new.is_empty() {
artists.push(new);
}
}
artists
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case::no_separation("Foo & Bar", &[], &[], vec!["Foo & Bar"])]
#[case::redundant_separation("Foo & Bar", &["&", " ", " &", " & "], &[], vec!["Foo", "Bar"])]
#[case::separation_no_exclusions("Foo & BarBaz", &["&", ";"], &[], vec!["Foo","BarBaz"])]
#[case::separation_no_exclusions("Foo & Bar; Baz", &["&", ";"], &[], vec!["Foo", "Bar", "Baz"])]
#[case::separation_excluded("Foo & Bar", &["&", ";"], &["Foo & Bar"], vec!["Foo & Bar"])]
#[case::separation_excluded("Foo & BarBaz", &["&", ";"], &["Foo & Bar"], vec!["Foo","BarBaz"])]
#[case::separation_excluded("Foo & Bar; Baz", &["&", ";"], &["Foo & Bar"], vec!["Foo & Bar", "Baz"])]
#[case::separation_excluded("Foo & BarBaz; Zing", &["&", ";"], &["Foo & Bar"], vec!["Foo","BarBaz", "Zing"])]
#[case::separation_excluded("Zing; Foo & BarBaz", &["&", ";"], &["Foo & Bar"], vec!["Zing","Foo","BarBaz"])]
#[case::separation_excluded("Foo & Bar; Baz; Zing", &["&", ";"], &["Foo & Bar"], vec!["Foo & Bar", "Baz", "Zing"])]
fn test_split_artist_name(
#[case] artist: &str,
#[case] separators: &[&str],
#[case] exceptions: &[&str],
#[case] expected: Vec<&str>,
) {
let separators = separators.iter().map(|s| (*s).to_string()).collect();
let exceptions = exceptions.iter().map(|s| (*s).to_string()).collect();
let expected = expected
.into_iter()
.map(std::string::ToString::to_string)
.collect::<OneOrMany<String>>();
let artists = split_artist_name(artist, &OneOrMany::Many(separators), &exceptions);
assert_eq!(artists, expected);
}
}