Add artist members and multiple writers
parent
cc949e07f3
commit
db3641200c
36
library.json
36
library.json
|
@ -1,21 +1,47 @@
|
||||||
{
|
{
|
||||||
"Taylor Swift": [
|
"artists": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Swift",
|
||||||
|
"members": [
|
||||||
|
"Taylor Swift"
|
||||||
|
],
|
||||||
|
"catalog": [
|
||||||
{
|
{
|
||||||
"title": "Love Story",
|
"title": "Love Story",
|
||||||
"composer": "Taylor Swift",
|
"writers": [
|
||||||
|
"Taylor Swift"
|
||||||
|
],
|
||||||
"performer": "Taylor Swift"
|
"performer": "Taylor Swift"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "black midi",
|
||||||
|
"members": [
|
||||||
|
"Cameron Picton",
|
||||||
|
"Matt Kelvin",
|
||||||
|
"Morgan Simpson",
|
||||||
|
"Geordie Greep"
|
||||||
],
|
],
|
||||||
"black midi": [
|
"catalog": [
|
||||||
{
|
{
|
||||||
"title": "Love Story",
|
"title": "Love Story",
|
||||||
"composer": "Taylor Swift",
|
"writers": [
|
||||||
|
"Taylor Swift"
|
||||||
|
],
|
||||||
"performer": "black midi"
|
"performer": "black midi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Love Story",
|
"title": "Love Story",
|
||||||
"composer": "black midi",
|
"writers": [
|
||||||
|
"Cameron Picton",
|
||||||
|
"Matt Kelvin",
|
||||||
|
"Morgan Simpson",
|
||||||
|
"Geordie Greep"
|
||||||
|
],
|
||||||
"performer": "black midi"
|
"performer": "black midi"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
144
src/library.rs
144
src/library.rs
|
@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue