Add artist members and multiple writers

main
mat ess 2023-04-18 00:46:55 -04:00
parent cc949e07f3
commit db3641200c
3 changed files with 158 additions and 54 deletions

View File

@ -1,21 +1,47 @@
{ {
"Taylor Swift": [ "artists": [
{ {
"title": "Love Story", "name": "Taylor Swift",
"composer": "Taylor Swift", "members": [
"performer": "Taylor Swift" "Taylor Swift"
} ],
], "catalog": [
"black midi": [ {
{ "title": "Love Story",
"title": "Love Story", "writers": [
"composer": "Taylor Swift", "Taylor Swift"
"performer": "black midi" ],
"performer": "Taylor Swift"
}
]
}, },
{ {
"title": "Love Story", "name": "black midi",
"composer": "black midi", "members": [
"performer": "black midi" "Cameron Picton",
"Matt Kelvin",
"Morgan Simpson",
"Geordie Greep"
],
"catalog": [
{
"title": "Love Story",
"writers": [
"Taylor Swift"
],
"performer": "black midi"
},
{
"title": "Love Story",
"writers": [
"Cameron Picton",
"Matt Kelvin",
"Morgan Simpson",
"Geordie Greep"
],
"performer": "black midi"
}
]
} }
] ]
} }

View File

@ -1,32 +1,16 @@
use nanoserde::DeJson; use nanoserde::DeJson;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::HashSet,
hash::Hash, hash::{Hash, Hasher},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone, DeJson)]
pub struct Library { pub struct Library {
artists: HashSet<Artist>, artists: HashSet<Artist>,
} }
impl Library { impl Library {
pub fn new(json: HashMap<String, Vec<Song>>) -> Self {
let artists = json
.into_iter()
.map(|(name, catalog)| Artist {
name,
catalog: catalog.into_iter().collect(),
})
.collect();
Library { artists }
}
pub fn find_covers(&self) -> HashSet<&Song> { pub fn find_covers(&self) -> HashSet<&Song> {
let names = self
.artists
.iter()
.map(|artist| artist.name.clone())
.collect::<HashSet<_>>();
self.artists self.artists
.iter() .iter()
.fold(HashSet::new(), |mut covers, artist| { .fold(HashSet::new(), |mut covers, artist| {
@ -34,19 +18,20 @@ impl Library {
covers covers
}) })
.into_iter() .into_iter()
.filter(|song| names.contains(&song.composer)) .filter(|song| song.is_library_cover(self))
.collect() .collect()
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, DeJson)]
struct Artist { struct Artist {
name: String, name: String,
members: HashSet<String>,
catalog: HashSet<Song>, catalog: HashSet<Song>,
} }
impl Hash for Artist { impl Hash for Artist {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state); self.name.hash(state);
} }
} }
@ -55,27 +40,61 @@ impl Artist {
fn covered_songs(&self) -> HashSet<&Song> { fn covered_songs(&self) -> HashSet<&Song> {
self.catalog self.catalog
.iter() .iter()
.filter(|song| song.composer != self.name) .filter(|song| song.is_covered_by(self))
.collect() .collect()
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, DeJson)] #[derive(Debug, Clone, PartialEq, Eq, DeJson)]
pub struct Song { pub struct Song {
pub title: String, pub title: String,
pub performer: String, pub performer: String,
pub composer: String, pub writers: HashSet<String>,
}
impl Hash for Song {
fn hash<H: Hasher>(&self, state: &mut H) {
self.title.hash(state);
self.performer.hash(state);
}
}
impl Song {
fn is_library_cover(&self, library: &Library) -> bool {
library
.artists
.iter()
.any(|artist| self.is_cover_of(artist))
}
fn is_covered_by(&self, artist: &Artist) -> bool {
self.performer == artist.name && self.writers.is_disjoint(&artist.members)
}
fn is_cover_of(&self, artist: &Artist) -> bool {
self.performer != artist.name && !self.writers.is_disjoint(&artist.members)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
fn bm_members() -> HashSet<String> {
[
"Cameron Picton".to_string(),
"Matt Kelvin".to_string(),
"Morgan Simpson".to_string(),
"Geordie Greep".to_string(),
]
.into()
}
fn bmbmbm() -> Song { fn bmbmbm() -> Song {
Song { Song {
title: "bmbmbm".to_string(), title: "bmbmbm".to_string(),
performer: "black midi".to_string(), performer: "black midi".to_string(),
composer: "black midi".to_string(), writers: bm_members(),
} }
} }
@ -83,7 +102,7 @@ mod tests {
Song { Song {
title: "Love Story".to_string(), title: "Love Story".to_string(),
performer: performer.to_string(), performer: performer.to_string(),
composer: "Taylor Swift".to_string(), writers: ["Taylor Swift".to_string()].into(),
} }
} }
@ -91,28 +110,75 @@ mod tests {
Song { Song {
title: "21st Century Schizoid Man".to_string(), title: "21st Century Schizoid Man".to_string(),
performer: "black midi".to_string(), performer: "black midi".to_string(),
composer: "Greg Lake, Ian McDonald, Michael Giles, Peter Sinfield & Robert Fripp" writers: [
.to_string(), "Greg Lake".to_string(),
"Ian McDonald".to_string(),
"Michael Giles".to_string(),
"Peter Sinfield".to_string(),
"Robert Fripp".to_string(),
]
.into(),
}
}
fn exile() -> Song {
Song {
title: "Exile".to_string(),
performer: "Taylor Swift".to_string(),
writers: [
"Taylor Swift".to_string(),
"William Bowery".to_string(),
"Justin Vernon".to_string(),
]
.into(),
} }
} }
fn bm() -> Artist { fn bm() -> Artist {
Artist { Artist {
name: "black midi".to_string(), name: "black midi".to_string(),
catalog: HashSet::from([bmbmbm(), love_story("black midi"), schizoid_man()]), members: bm_members(),
catalog: [bmbmbm(), love_story("black midi"), schizoid_man()].into(),
} }
} }
fn ts() -> Artist { fn ts() -> Artist {
Artist { Artist {
name: "Taylor Swift".to_string(), name: "Taylor Swift".to_string(),
catalog: HashSet::from([love_story("Taylor Swift")]), members: ["Taylor Swift".to_string()].into(),
catalog: [love_story("Taylor Swift"), exile()].into(),
}
}
fn bon_iver() -> Artist {
Artist {
name: "Bon Iver".to_string(),
members: [
"Justin Vernon".to_string(),
"Sean Carey".to_string(),
"Michael Noyce".to_string(),
"Matthew McCaughan".to_string(),
]
.into(),
catalog: [
Song {
title: "Skinny Love".to_string(),
performer: "Bon Iver".to_string(),
writers: ["Justin Vernon".to_string()].into(),
},
Song {
title: "Holocene".to_string(),
performer: "Bon Iver".to_string(),
writers: ["Justin Vernon".to_string()].into(),
},
]
.into(),
} }
} }
fn library() -> Library { fn library() -> Library {
Library { Library {
artists: HashSet::from([bm(), ts()]), artists: HashSet::from([bm(), ts(), bon_iver()]),
} }
} }
@ -121,8 +187,22 @@ mod tests {
assert!(bm().covered_songs() == HashSet::from([&love_story("black midi"), &schizoid_man()])) assert!(bm().covered_songs() == HashSet::from([&love_story("black midi"), &schizoid_man()]))
} }
#[test]
fn test_covered_songs_no_covers() {
assert!(bon_iver().covered_songs().is_empty())
}
#[test] #[test]
fn test_find_covers() { fn test_find_covers() {
assert!(library().find_covers() == HashSet::from([&love_story("black midi")])) assert!(library().find_covers() == HashSet::from([&love_story("black midi")]))
} }
#[test]
fn test_find_covers_one_artist() {
assert!(Library {
artists: [bon_iver()].into()
}
.find_covers()
.is_empty())
}
} }

View File

@ -1,22 +1,20 @@
use nanoserde::DeJson; use nanoserde::DeJson;
use std::{collections::HashMap, env, fs}; use std::{env, fs};
mod library; mod library;
use crate::library::{Library, Song};
fn main() { fn main() {
let file_path = env::args() let file_path = env::args()
.nth(1) .nth(1)
.expect("Usage: cover path/to/library.json"); .expect("Usage: cover path/to/library.json");
let data = fs::read_to_string(file_path).expect("Failed to open library file"); let json = fs::read_to_string(file_path).expect("Failed to open library file");
let json: HashMap<String, Vec<Song>> = let library: library::Library =
DeJson::deserialize_json(&data).expect("Failed to deserialize library"); DeJson::deserialize_json(&json).expect("Failed to deserialize library file");
let library = Library::new(json);
for cover in library.find_covers() { for cover in library.find_covers() {
println!("Found cover!"); println!("Found cover!");
println!( println!(
"{}, by {}, covered by {}", "{}, by {:?}, covered by {}",
cover.title, cover.composer, cover.performer cover.title, cover.writers, cover.performer
) )
} }
} }