diff --git a/library.json b/library.json index a4a1841..bc62b08 100644 --- a/library.json +++ b/library.json @@ -1,21 +1,47 @@ { - "Taylor Swift": [ + "artists": [ { - "title": "Love Story", - "composer": "Taylor Swift", - "performer": "Taylor Swift" - } - ], - "black midi": [ - { - "title": "Love Story", - "composer": "Taylor Swift", - "performer": "black midi" + "name": "Taylor Swift", + "members": [ + "Taylor Swift" + ], + "catalog": [ + { + "title": "Love Story", + "writers": [ + "Taylor Swift" + ], + "performer": "Taylor Swift" + } + ] }, { - "title": "Love Story", - "composer": "black midi", - "performer": "black midi" + "name": "black midi", + "members": [ + "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" + } + ] } ] } diff --git a/src/library.rs b/src/library.rs index d4db4ab..b7ac417 100644 --- a/src/library.rs +++ b/src/library.rs @@ -1,32 +1,16 @@ use nanoserde::DeJson; use std::{ - collections::{HashMap, HashSet}, - hash::Hash, + collections::HashSet, + hash::{Hash, Hasher}, }; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, DeJson)] pub struct Library { artists: HashSet, } impl Library { - pub fn new(json: HashMap>) -> 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> { - let names = self - .artists - .iter() - .map(|artist| artist.name.clone()) - .collect::>(); self.artists .iter() .fold(HashSet::new(), |mut covers, artist| { @@ -34,19 +18,20 @@ impl Library { covers }) .into_iter() - .filter(|song| names.contains(&song.composer)) + .filter(|song| song.is_library_cover(self)) .collect() } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, DeJson)] struct Artist { name: String, + members: HashSet, catalog: HashSet, } impl Hash for Artist { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { self.name.hash(state); } } @@ -55,27 +40,61 @@ impl Artist { fn covered_songs(&self) -> HashSet<&Song> { self.catalog .iter() - .filter(|song| song.composer != self.name) + .filter(|song| song.is_covered_by(self)) .collect() } } -#[derive(Debug, Clone, PartialEq, Eq, Hash, DeJson)] +#[derive(Debug, Clone, PartialEq, Eq, DeJson)] pub struct Song { pub title: String, pub performer: String, - pub composer: String, + pub writers: HashSet, +} + +impl Hash for Song { + fn hash(&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)] mod tests { use super::*; + fn bm_members() -> HashSet { + [ + "Cameron Picton".to_string(), + "Matt Kelvin".to_string(), + "Morgan Simpson".to_string(), + "Geordie Greep".to_string(), + ] + .into() + } + fn bmbmbm() -> Song { Song { title: "bmbmbm".to_string(), performer: "black midi".to_string(), - composer: "black midi".to_string(), + writers: bm_members(), } } @@ -83,7 +102,7 @@ mod tests { Song { title: "Love Story".to_string(), performer: performer.to_string(), - composer: "Taylor Swift".to_string(), + writers: ["Taylor Swift".to_string()].into(), } } @@ -91,28 +110,75 @@ mod tests { Song { title: "21st Century Schizoid Man".to_string(), performer: "black midi".to_string(), - composer: "Greg Lake, Ian McDonald, Michael Giles, Peter Sinfield & Robert Fripp" - .to_string(), + writers: [ + "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 { Artist { 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 { Artist { 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 { 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()])) } + #[test] + fn test_covered_songs_no_covers() { + assert!(bon_iver().covered_songs().is_empty()) + } + #[test] fn test_find_covers() { 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()) + } } diff --git a/src/main.rs b/src/main.rs index dcfa765..4a8a1bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,20 @@ use nanoserde::DeJson; -use std::{collections::HashMap, env, fs}; +use std::{env, fs}; mod library; -use crate::library::{Library, Song}; fn main() { let file_path = env::args() .nth(1) .expect("Usage: cover path/to/library.json"); - let data = fs::read_to_string(file_path).expect("Failed to open library file"); - let json: HashMap> = - DeJson::deserialize_json(&data).expect("Failed to deserialize library"); - let library = Library::new(json); + let json = fs::read_to_string(file_path).expect("Failed to open library file"); + let library: library::Library = + DeJson::deserialize_json(&json).expect("Failed to deserialize library file"); for cover in library.find_covers() { println!("Found cover!"); println!( - "{}, by {}, covered by {}", - cover.title, cover.composer, cover.performer + "{}, by {:?}, covered by {}", + cover.title, cover.writers, cover.performer ) } }