diff --git a/Cargo.lock b/Cargo.lock index 07cd910..281d4ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,9 +64,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -117,9 +120,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", ] @@ -256,9 +259,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "iana-time-zone" @@ -285,9 +288,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", @@ -329,9 +332,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "log" @@ -523,18 +526,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", @@ -543,9 +546,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -553,11 +556,17 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", diff --git a/benches/search.rs b/benches/search.rs index f250cf0..d73d12c 100644 --- a/benches/search.rs +++ b/benches/search.rs @@ -2,30 +2,22 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; #[allow(unused_imports)] use endsong::prelude::*; +use itertools::Itertools; -fn paths() -> [String; 10] { +fn paths(first: usize, last: usize) -> Vec { let root = match std::env::consts::OS { - "windows" => r"C:\\Temp\\Endsong\\", + "windows" => r"C:\Temp\Endsong\", "macos" => "/Users/filip/Other/Endsong/", _ => "/mnt/c/temp/Endsong/", }; - [ - format!("{root}endsong_0.json"), - format!("{root}endsong_1.json"), - format!("{root}endsong_2.json"), - format!("{root}endsong_3.json"), - format!("{root}endsong_4.json"), - format!("{root}endsong_5.json"), - format!("{root}endsong_6.json"), - format!("{root}endsong_7.json"), - format!("{root}endsong_8.json"), - format!("{root}endsong_9.json"), - ] + (first..=last) + .map(|i| format!("{root}endsong_{i}.json")) + .collect() } #[allow(dead_code)] fn lol(c: &mut Criterion) { - let entries = black_box(SongEntries::new(&paths()[..=2]).unwrap()); + let entries = black_box(SongEntries::new(&paths(0, 2)).unwrap()); c.bench_function("artists_vec", |c| { c.iter(|| { @@ -51,7 +43,7 @@ fn lol(c: &mut Criterion) { #[allow(dead_code)] fn kekw(c: &mut Criterion) { - let entries = black_box(SongEntries::new(&paths()[7..=9]).unwrap()); + let entries = black_box(SongEntries::new(&paths(7, 9)).unwrap()); let lth = Song::new( "Last Train Home", @@ -81,7 +73,7 @@ fn kekw(c: &mut Criterion) { #[allow(dead_code)] fn parse(c: &mut Criterion) { - let paths = paths(); + let paths = paths(0, 9); c.bench_function("parse", |c| { c.iter(|| { @@ -175,7 +167,7 @@ fn unique_sum(c: &mut Criterion) { #[allow(dead_code)] fn gather(c: &mut Criterion) { - let entries = black_box(SongEntries::new(&paths()[..=0]).unwrap()); + let entries = black_box(SongEntries::new(&paths(0, 0)).unwrap()); c.bench_function("gather artists", |c| { c.iter(|| { @@ -189,7 +181,7 @@ fn gather(c: &mut Criterion) { }); c.bench_function("gather songs", |c| { c.iter(|| { - black_box(gather::songs(&entries, true)); + black_box(gather::songs_summed_across_albums(&entries)); }) }); } @@ -199,7 +191,7 @@ fn capitalization(c: &mut Criterion) { c.bench_function("parse and sum diff capitalization", |c| { c.iter(|| { black_box( - SongEntries::new(&paths()[..=0]) + SongEntries::new(&paths(0, 0)) .unwrap() .sum_different_capitalization(), ); @@ -207,10 +199,84 @@ fn capitalization(c: &mut Criterion) { }); } +#[allow(dead_code)] +fn find(c: &mut Criterion) { + let entries = black_box(SongEntries::new(&paths(0, 9)).unwrap()); + let usr_song_one = Song::new("MUKANJYO", "MUKANJYO", "Survive Said The Prophet"); + let usr_song_two = Song::new("MUKANJYO", "Mukanjyo", "Survive Said The Prophet"); + + let one = entries + .iter() + .find(|entry| usr_song_one.is_entry_lowercase(entry)) + .map(Song::from) + .unwrap(); + let two = entries + .iter() + .find(|entry| usr_song_two.is_entry_lowercase(entry)) + .map(Song::from) + .unwrap(); + dbg!(one, two); + + let (song_name, artist_name) = ( + usr_song_one.name.to_lowercase(), + usr_song_one.album.artist.name.to_lowercase(), + ); + + let onetwo = entries + .iter() + .filter(|entry| { + entry.track.to_lowercase() == song_name && entry.artist.to_lowercase() == artist_name + }) + .unique() + .map(Song::from) + .collect_vec(); + dbg!(onetwo); + + c.bench_function("find v1", |c| { + c.iter(|| { + entries + .iter() + .find(|entry| usr_song_one.is_entry_lowercase(entry)) + .map(Song::from) + }) + }); + c.bench_function("find v2", |c| { + c.iter(|| { + entries + .iter() + .filter(|entry| usr_song_one.is_entry_lowercase(entry)) + .map(Song::from) + .unique() + .collect_vec() + }) + }); + + let art = Artist::new("survive said the prophet"); + c.bench_function("find artist v1", |c| { + c.iter(|| { + entries + .iter() + .find(|entry| art.is_entry_lowercase(entry)) + .map(Artist::from) + }) + }); + c.bench_function("find artist v2", |c| { + c.iter(|| { + entries + .iter() + .filter(|entry| art.is_entry_lowercase(entry)) + .map(Artist::from) + .unique() + .collect_vec() + }) + }); +} + // criterion_group!(benches, lol); // criterion_group!(benches, kekw); -criterion_group!(benches, parse); +// criterion_group!(benches, parse); // criterion_group!(benches, unique_sum); // criterion_group!(benches, gather); // criterion_group!(benches, capitalization); +criterion_group!(benches, find); criterion_main!(benches); diff --git a/endsong_ui/Cargo.lock b/endsong_ui/Cargo.lock index d622df1..f72e598 100644 --- a/endsong_ui/Cargo.lock +++ b/endsong_ui/Cargo.lock @@ -131,9 +131,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -191,9 +194,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", ] @@ -479,9 +482,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -560,9 +563,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", @@ -610,9 +613,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -1014,18 +1017,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", @@ -1034,9 +1037,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -1094,6 +1097,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "smallvec" version = "1.13.2" @@ -1114,9 +1123,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -1268,9 +1277,9 @@ dependencies = [ [[package]] name = "typeid" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" [[package]] name = "unicase" diff --git a/endsong_ui/src/main.rs b/endsong_ui/src/main.rs index f6663aa..7179c19 100644 --- a/endsong_ui/src/main.rs +++ b/endsong_ui/src/main.rs @@ -37,7 +37,7 @@ fn main() { "macos" => "/Users/filip/Other/Endsong/", _ => "/mnt/c/temp/Endsong/", }; - let last: u8 = 9; + let last: u8 = 0; let paths: Vec = (0..=last) .map(|i| format!("{root}endsong_{i}.json")) .collect(); diff --git a/endsong_ui/src/ui/mod.rs b/endsong_ui/src/ui/mod.rs index a255b52..7ba97a0 100644 --- a/endsong_ui/src/ui/mod.rs +++ b/endsong_ui/src/ui/mod.rs @@ -64,6 +64,9 @@ enum UiError { /// Used when absurdly high time period would lead to panic (shouldn't happen) #[error("Use a sane time period")] TimeDeltaOverflow, + /// Used when you don't want an `.unwrap()` but should never happen + #[error("Should never occur! Something very bad happened!")] + Unreachable, } /// Helper for [`Editor`] @@ -959,9 +962,29 @@ fn read_artist( rl.helper_mut().unwrap().complete_list(entries.artists()); println!("Artist name?"); let usr_input_art = rl.readline(PROMPT_MAIN)?; - entries - .find() - .artist(&usr_input_art) + let artists = entries.find().artist(&usr_input_art); + + let Some(artists) = artists else { + return Err(UiError::NotFound("artist")); + }; + + if artists.len() == 1 { + return artists.into_iter().next().ok_or(UiError::Unreachable); + } + + println!("There's multiple artists with that name, but capitalized differently! Which one do you mean:"); + for artist in &artists { + println!("{artist}"); + } + + // prompt: artist name exact + rl.helper_mut() + .unwrap() + .complete_list(artists.iter().map(|art| Rc::clone(&art.name)).collect()); + let usr_input_art = rl.readline(PROMPT_MAIN)?; + artists + .into_iter() + .find(|art| usr_input_art == art.name.as_ref()) .ok_or(UiError::NotFound("artist")) } @@ -975,9 +998,30 @@ fn read_album( rl.helper_mut().unwrap().complete_list(entries.albums(art)); println!("Album name?"); let usr_input_alb = rl.readline(PROMPT_MAIN)?; - entries - .find() - .album(&usr_input_alb, &art.name) + let albums = entries.find().album(&usr_input_alb, &art.name); + + let Some(albums) = albums else { + return Err(UiError::NotFound("album from this artist")); + }; + + if albums.len() == 1 { + return albums.into_iter().next().ok_or(UiError::Unreachable); + } + + // should only happen if you didn't do SongEntries::sum_different_capitalization() + println!("There's multiple albums from this artist with that name, but capitalized differently! Which one do you mean:"); + for album in &albums { + println!("{}", album.name); + } + + // prompt: artist name exact + rl.helper_mut() + .unwrap() + .complete_list(albums.iter().map(|alb| Rc::clone(&alb.name)).collect()); + let usr_input_alb = rl.readline(PROMPT_MAIN)?; + albums + .into_iter() + .find(|alb| usr_input_alb == alb.name.as_ref()) .ok_or(UiError::NotFound("album from this artist")) } @@ -991,9 +1035,32 @@ fn read_song( rl.helper_mut().unwrap().complete_list(entries.songs(alb)); println!("Song name?"); let usr_input_son = rl.readline(PROMPT_MAIN)?; - entries + let songs = entries .find() - .song_from_album(&usr_input_son, &alb.name, &alb.artist.name) + .song_from_album(&usr_input_son, &alb.name, &alb.artist.name); + + let Some(songs) = songs else { + return Err(UiError::NotFound("song from this album")); + }; + + if songs.len() == 1 { + return songs.into_iter().next().ok_or(UiError::Unreachable); + } + + // should only happen if you didn't do SongEntries::sum_different_capitalization() + println!("There's multiple songs from this album with that name, but capitalized differently! Which one do you mean:"); + for song in &songs { + println!("{}", song.name); + } + + // prompt: artist name exact + rl.helper_mut() + .unwrap() + .complete_list(songs.iter().map(|som| Rc::clone(&som.name)).collect()); + let usr_input_son = rl.readline(PROMPT_MAIN)?; + songs + .into_iter() + .find(|son| usr_input_son == son.name.as_ref()) .ok_or(UiError::NotFound("song from this album")) } diff --git a/src/aspect.rs b/src/aspect.rs index ef5223b..f508e45 100644 --- a/src/aspect.rs +++ b/src/aspect.rs @@ -67,6 +67,10 @@ pub trait Music: Display + Clone + Eq + Ord { pub trait HasSongs: Music {} /// Struct for representing an artist +/// +/// Usually, you don't want to create this yourself, but rather use +/// [`SongEntries::find`][crate::entry::SongEntries::find] to guarantee +/// it's in the dataset #[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Artist { /// Name of the artist @@ -74,6 +78,10 @@ pub struct Artist { } impl Artist { /// Creates an instance of Artist + /// + /// Usually, you don't want to use this yourself, but rather use + /// [`SongEntries::find`][crate::entry::SongEntries::find] to guarantee + /// it's in the dataset pub fn new>>(artist_name: S) -> Artist { Artist { name: artist_name.into(), @@ -149,6 +157,10 @@ impl Music for Artist { impl HasSongs for Artist {} /// Struct for representing an album +/// +/// Usually, you don't want to create this yourself, but rather use +/// [`SongEntries::find`][crate::entry::SongEntries::find] to guarantee +/// it's in the dataset #[derive(PartialEq, Eq, Hash, Debug)] pub struct Album { /// Name of the album @@ -158,6 +170,10 @@ pub struct Album { } impl Album { /// Creates an instance of Album + /// + /// Usually, you don't want to use this yourself, but rather use + /// [`SongEntries::find`][crate::entry::SongEntries::find] to guarantee + /// it's in the dataset pub fn new>>(album_name: S, artist_name: S) -> Album { Album { name: album_name.into(), @@ -250,6 +266,10 @@ impl Music for Album { impl HasSongs for Album {} /// Struct for representing a song +/// +/// Usually, you don't want to create this yourself, but rather use +/// [`SongEntries::find`][crate::entry::SongEntries::find] to guarantee +/// it's in the dataset #[derive(PartialEq, Eq, Hash, Debug)] pub struct Song { /// Name of the song @@ -260,6 +280,10 @@ pub struct Song { } impl Song { /// Creates an instance of Song + /// + /// Usually, you don't want to use this yourself, but rather use + /// [`SongEntries::find`][crate::entry::SongEntries::find] to guarantee + /// it's in the dataset pub fn new>>(song_name: S, album_name: S, artist_name: S) -> Song { Song { name: song_name.into(), diff --git a/src/entry.rs b/src/entry.rs index 3cfbff0..1ef89a4 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -445,8 +445,8 @@ impl SongEntries { /// Adds search capability /// - /// Use with methods from [`Find`]: [`.artist()`][Find::artist()], [`.album()`][Find::album()], - /// [`.song_from_album()`][Find::song_from_album()] and [`.song()`][Find::song()] + /// Use with methods from [`Find`] to search for valid artist, album or song + /// names from the dataset and more. #[must_use] pub fn find(&self) -> Find { Find(self) @@ -523,33 +523,46 @@ fn song_durations(entries: &Vec) -> HashMap { /// Created with [`SongEntries::find`] pub struct Find<'a>(&'a SongEntries); impl<'a> Find<'a> { - /// Searches the entries for if the given artist exists in the dataset + /// Searches the entries for possible artists /// - /// Case-insensitive and returns the [`Artist`] with proper capitalization - /// (i.e. the capitalization of the first entry it finds) + /// Case-insensitive and returns the [`Artist`] with proper capitalization. + /// The vector contains multiple [`Artist`]s if they're called the same, + /// but their names are capitalized diffferently + /// + /// Vector is guaranteed to be non-empty if [`Some`] /// /// See #2 #[must_use] - pub fn artist(&self, artist_name: &str) -> Option { + pub fn artist(&self, artist_name: &str) -> Option> { find::artist(self.0, artist_name) } - /// Searches the entries for if the given album exists in the dataset + /// Searches the entries for possible albums /// /// Case-insensitive and returns the [`Album`] with proper capitalization - /// (i.e. the capitalization of the first entry it finds) + /// The vector contains multiple [`Album`]s if they're called the same, + /// but their names are capitalized diffferently + /// (Guaranteed for there to be only one version if you use + /// [`SongEntries::sum_different_capitalization`][crate::entry::SongEntries::sum_different_capitalization]) + /// + /// Vector is guaranteed to be non-empty if [`Some`] /// /// See #2 #[must_use] - pub fn album(&self, album_name: &str, artist_name: &str) -> Option { + pub fn album(&self, album_name: &str, artist_name: &str) -> Option> { find::album(self.0, album_name, artist_name) } - /// Searches the entries for if the given song (in that specific album) - /// exists in the dataset + /// Searches the entries possible songs (in that specific album) + /// in the dataset /// /// Case-insensitive and returns the [`Song`] with proper capitalization - /// (i.e. the capitalization of the first entry it finds) + /// The vector contains multiple [`Song`]s if they're called the same, + /// but their names are capitalized diffferently + /// (Guaranteed for there to be only one version if you use + /// [`SongEntries::sum_different_capitalization`][crate::entry::SongEntries::sum_different_capitalization]) + /// + /// Vector is guaranteed to be non-empty if [`Some`] /// /// See #2 #[must_use] @@ -558,7 +571,7 @@ impl<'a> Find<'a> { song_name: &str, album_name: &str, artist_name: &str, - ) -> Option { + ) -> Option> { find::song_from_album(self.0, song_name, album_name, artist_name) } diff --git a/src/find.rs b/src/find.rs index 0650353..718074d 100644 --- a/src/find.rs +++ b/src/find.rs @@ -5,41 +5,70 @@ use itertools::Itertools; use crate::aspect::{Album, Artist, Music, Song}; use crate::entry::SongEntry; -/// Searches the entries for if the given artist exists in the dataset +/// Searches the entries for possible artists /// -/// Case-insensitive and returns the [`Artist`] with proper capitalization -/// (i.e. the capitalization of the first entry it finds) +/// Case-insensitive and returns the [`Artist`] with proper capitalization. +/// The vector contains multiple [`Artist`]s if they're called the same, +/// but their names are capitalized diffferently +/// +/// Vector is guaranteed to be non-empty if [`Some`] /// /// See #2 -pub fn artist(entries: &[SongEntry], artist_name: &str) -> Option { +pub fn artist(entries: &[SongEntry], artist_name: &str) -> Option> { let usr_artist = Artist::new(artist_name); - entries + let artists = entries .iter() - .find(|entry| usr_artist.is_entry_lowercase(entry)) + .filter(|entry| usr_artist.is_entry_lowercase(entry)) .map(Artist::from) + .unique() + .collect_vec(); + + if artists.is_empty() { + return None; + } + + Some(artists) } -/// Searches the entries for if the given album exists in the dataset +/// Searches the entries for possible albums /// /// Case-insensitive and returns the [`Album`] with proper capitalization -/// (i.e. the capitalization of the first entry it finds) +/// The vector contains multiple [`Album`]s if they're called the same, +/// but their names are capitalized diffferently +/// (Guaranteed for there to be only one version if you use +/// [`SongEntries::sum_different_capitalization`][crate::entry::SongEntries::sum_different_capitalization]) +/// +/// Vector is guaranteed to be non-empty if [`Some`] /// /// See #2 -pub fn album(entries: &[SongEntry], album_name: &str, artist_name: &str) -> Option { +pub fn album(entries: &[SongEntry], album_name: &str, artist_name: &str) -> Option> { let usr_album = Album::new(album_name, artist_name); - entries + let albums = entries .iter() - .find(|entry| usr_album.is_entry_lowercase(entry)) + .filter(|entry| usr_album.is_entry_lowercase(entry)) .map(Album::from) + .unique() + .collect_vec(); + + if albums.is_empty() { + return None; + } + + Some(albums) } -/// Searches the entries for if the given song (in that specific album) -/// exists in the dataset +/// Searches the entries possible songs (in that specific album) +/// in the dataset /// /// Case-insensitive and returns the [`Song`] with proper capitalization -/// (i.e. the capitalization of the first entry it finds) +/// The vector contains multiple [`Song`]s if they're called the same, +/// but their names are capitalized diffferently +/// (Guaranteed for there to be only one version if you use +/// [`SongEntries::sum_different_capitalization`][crate::entry::SongEntries::sum_different_capitalization]) +/// +/// Vector is guaranteed to be non-empty if [`Some`] /// /// See #2 pub fn song_from_album( @@ -47,16 +76,25 @@ pub fn song_from_album( song_name: &str, album_name: &str, artist_name: &str, -) -> Option { +) -> Option> { let usr_song = Song::new(song_name, album_name, artist_name); - entries + let songs = entries .iter() - .find(|entry| usr_song.is_entry_lowercase(entry)) + .filter(|entry| usr_song.is_entry_lowercase(entry)) .map(Song::from) + .unique() + .collect_vec(); + + if songs.is_empty() { + return None; + } + + Some(songs) } /// Searches the dataset for multiple versions of a song +/// (i.e. if a song with the same name is in multiple albums) /// /// Case-insensitive and returns a [`Vec`] containing an instance /// of [`Song`] for every album it's been found in with proper capitalization @@ -70,8 +108,8 @@ pub fn song(entries: &[SongEntry], song_name: &str, artist_name: &str) -> Option .filter(|entry| { entry.track.to_lowercase() == song_name && entry.artist.to_lowercase() == artist_name }) - .unique() .map(Song::from) + .unique() .collect_vec(); if song_versions.is_empty() { @@ -109,7 +147,7 @@ mod tests { let entries = crate::entry::SongEntries::new(&paths).unwrap(); assert_eq!( - artist(&entries, "Theocracy").unwrap(), + artist(&entries, "Theocracy").unwrap()[0], Artist::new("Theocracy") ); assert!(entries.find().artist("Powerwolf").is_none());