89 lines
2.1 KiB
Rust
89 lines
2.1 KiB
Rust
use nanoserde::DeJson;
|
|
use std::{
|
|
collections::{HashMap, HashSet},
|
|
env, fs,
|
|
hash::Hash,
|
|
};
|
|
|
|
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<String, Vec<Song>> =
|
|
DeJson::deserialize_json(&data).expect("Failed to deserialize library");
|
|
let library = Library::new(json);
|
|
for cover in library.find_covers() {
|
|
println!("Found cover!");
|
|
println!(
|
|
"{}, by {}, covered by {}",
|
|
cover.title, cover.composer, cover.performer
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct Library {
|
|
artists: HashSet<Artist>,
|
|
}
|
|
|
|
impl Library {
|
|
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 }
|
|
}
|
|
}
|
|
|
|
impl Library {
|
|
fn find_covers(&self) -> HashSet<&Song> {
|
|
let names = self
|
|
.artists
|
|
.iter()
|
|
.map(|artist| artist.name.clone())
|
|
.collect::<HashSet<_>>();
|
|
self.artists
|
|
.iter()
|
|
.fold(HashSet::new(), |mut covers, artist| {
|
|
covers.extend(artist.covered_songs());
|
|
covers
|
|
})
|
|
.into_iter()
|
|
.filter(|song| names.contains(&song.composer))
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
struct Artist {
|
|
name: String,
|
|
catalog: HashSet<Song>,
|
|
}
|
|
|
|
impl Hash for Artist {
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
self.name.hash(state);
|
|
}
|
|
}
|
|
|
|
impl Artist {
|
|
fn covered_songs(&self) -> HashSet<&Song> {
|
|
self.catalog
|
|
.iter()
|
|
.filter(|song| song.composer != self.name)
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, DeJson)]
|
|
struct Song {
|
|
title: String,
|
|
performer: String,
|
|
composer: String,
|
|
}
|