From 9192c4bdb48d5d14664d8cdcccd7fda3be0e3a63 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 13 Apr 2023 23:53:22 +0200 Subject: [PATCH] New way to do queries --- src/collection/mod.rs | 4 +- src/library/beets.rs | 141 ++++++++++++++++------------------------- src/library/mod.rs | 107 +++++++++++++------------------ tests/library/beets.rs | 14 ++-- 4 files changed, 108 insertions(+), 158 deletions(-) diff --git a/src/collection/mod.rs b/src/collection/mod.rs index a2ca0a3..b75f4a1 100644 --- a/src/collection/mod.rs +++ b/src/collection/mod.rs @@ -75,7 +75,7 @@ impl MhCollectionManager { impl CollectionManager for MhCollectionManager { fn rescan_library(&mut self) -> Result<(), Error> { - self.collection = self.library.list(&Query::default())?; + self.collection = self.library.list(&Query::new())?; Ok(()) } @@ -107,7 +107,7 @@ mod tests { let mut library = MockLibrary::new(); let mut database = MockDatabase::new(); - let library_input = Query::default(); + let library_input = Query::new(); let library_result = Ok(COLLECTION.to_owned()); let database_input = COLLECTION.to_owned(); diff --git a/src/library/beets.rs b/src/library/beets.rs index 91ff2d1..9bd9acb 100644 --- a/src/library/beets.rs +++ b/src/library/beets.rs @@ -3,7 +3,6 @@ use std::{ collections::{HashMap, HashSet}, - fmt::Display, path::{Path, PathBuf}, process::Command, str, @@ -14,7 +13,7 @@ use mockall::automock; use crate::{Album, AlbumId, Artist, ArtistId, Track, TrackFormat}; -use super::{Error, Library, Query, QueryOption}; +use super::{Error, Field, Library, Query}; macro_rules! list_format_separator { () => { @@ -43,70 +42,39 @@ const LIST_FORMAT_ARG: &str = concat!( const TRACK_FORMAT_FLAC: &str = "FLAC"; const TRACK_FORMAT_MP3: &str = "MP3"; -trait QueryOptionArgBeets { - fn to_arg(&self, option_name: &str) -> Option; +trait ToBeetsArg { + fn to_arg(&self, include: bool) -> String; } -trait QueryArgsBeets { +trait ToBeetsArgs { fn to_args(&self) -> Vec; } -trait SimpleOption {} -impl SimpleOption for String {} -impl SimpleOption for u32 {} - -impl QueryOptionArgBeets for QueryOption { - fn to_arg(&self, option_name: &str) -> Option { - let (negate, value) = match self { - Self::Include(value) => ("", value), - Self::Exclude(value) => ("^", value), - Self::None => return None, - }; - Some(format!("{negate}{option_name}{value}")) +impl ToBeetsArg for Field { + fn to_arg(&self, include: bool) -> String { + let negate = if include { "" } else { "^" }; + match self { + Field::AlbumArtist(ref s) => format!("{negate}albumartist:{s}"), + Field::AlbumYear(ref u) => format!("{negate}year:{u}"), + Field::AlbumTitle(ref s) => format!("{negate}album:{s}"), + Field::TrackNumber(ref u) => format!("{negate}track:{u}"), + Field::TrackTitle(ref s) => format!("{negate}title:{s}"), + Field::TrackArtist(ref v) => format!("{negate}artist:{}", v.join("; ")), + Field::All(ref s) => format!("{negate}{s}"), + } } } -impl QueryOptionArgBeets for QueryOption> { - fn to_arg(&self, option_name: &str) -> Option { - let (negate, vec) = match self { - Self::Include(value) => ("", value), - Self::Exclude(value) => ("^", value), - Self::None => return None, - }; - Some(format!("{negate}{option_name}{}", vec.join("; "))) - } -} - -impl QueryArgsBeets for Query { +impl ToBeetsArgs for Query { fn to_args(&self) -> Vec { let mut arguments: Vec = vec![]; - if let Some(album_artist) = self.album_artist.to_arg("albumartist:") { - arguments.push(album_artist); - }; + for field in self.include.iter() { + arguments.push(field.to_arg(true)); + } - if let Some(album_year) = self.album_year.to_arg("year:") { - arguments.push(album_year); - }; - - if let Some(album_title) = self.album_title.to_arg("album:") { - arguments.push(album_title); - }; - - if let Some(track_number) = self.track_number.to_arg("track:") { - arguments.push(track_number); - }; - - if let Some(track_title) = self.track_title.to_arg("title:") { - arguments.push(track_title); - }; - - if let Some(track_artist) = self.track_artist.to_arg("artist:") { - arguments.push(track_artist); - }; - - if let Some(all) = self.all.to_arg("") { - arguments.push(all); + for field in self.exclude.iter() { + arguments.push(field.to_arg(false)); } arguments @@ -328,40 +296,38 @@ mod tests { #[test] fn test_query() { - let query = Query::new() - .album_title(QueryOption::Exclude(String::from("some.album"))) - .track_number(QueryOption::Include(5)) - .track_artist(QueryOption::Include(vec![ - String::from("some.artist.1"), - String::from("some.artist.2"), - ])) - .all(QueryOption::Exclude(String::from("some.all"))); - assert_eq!( - query.to_args(), + Query::new() + .exclude(Field::AlbumTitle(String::from("some.album"))) + .include(Field::TrackNumber(5)) + .include(Field::TrackArtist(vec![ + String::from("some.artist.1"), + String::from("some.artist.2"), + ])) + .exclude(Field::All(String::from("some.all"))) + .to_args(), vec![ - String::from("^album:some.album"), String::from("track:5"), String::from("artist:some.artist.1; some.artist.2"), + String::from("^album:some.album"), String::from("^some.all"), ] ); - let query = Query::new() - .album_artist(QueryOption::Exclude(String::from("some.albumartist"))) - .album_year(QueryOption::Include(3030)) - .track_title(QueryOption::Include(String::from("some.track"))) - .track_artist(QueryOption::Exclude(vec![ - String::from("some.artist.1"), - String::from("some.artist.2"), - ])); - assert_eq!( - query.to_args(), + Query::default() + .exclude(Field::AlbumArtist(String::from("some.albumartist"))) + .include(Field::AlbumYear(3030)) + .include(Field::TrackTitle(String::from("some.track"))) + .exclude(Field::TrackArtist(vec![ + String::from("some.artist.1"), + String::from("some.artist.2"), + ])) + .to_args(), vec![ - String::from("^albumartist:some.albumartist"), String::from("year:3030"), String::from("title:some.track"), + String::from("^albumartist:some.albumartist"), String::from("^artist:some.artist.1; some.artist.2"), ] ); @@ -380,7 +346,7 @@ mod tests { .return_once(|_| result); let mut beets = BeetsLibrary::new(executor); - let output = beets.list(&Query::default()).unwrap(); + let output = beets.list(&Query::new()).unwrap(); let expected: Vec = vec![]; assert_eq!(output, expected); @@ -400,7 +366,7 @@ mod tests { .return_once(|_| result); let mut beets = BeetsLibrary::new(executor); - let output = beets.list(&Query::default()).unwrap(); + let output = beets.list(&Query::new()).unwrap(); assert_eq!(output, expected); } @@ -435,7 +401,7 @@ mod tests { .return_once(|_| result); let mut beets = BeetsLibrary::new(executor); - let output = beets.list(&Query::default()).unwrap(); + let output = beets.list(&Query::new()).unwrap(); assert_eq!(output, expected); } @@ -457,24 +423,25 @@ mod tests { .return_once(|_| result); let mut beets = BeetsLibrary::new(executor); - let output = beets.list(&Query::default()).unwrap(); + let output = beets.list(&Query::new()).unwrap(); assert_eq!(output, expected); } #[test] fn test_list_query() { - let query = Query::new() - .album_title(QueryOption::Exclude(String::from("some.album"))) - .track_number(QueryOption::Include(5)) - .track_artist(QueryOption::Include(vec![String::from("some.artist")])); + let mut query = Query::new(); + query + .exclude(Field::AlbumTitle(String::from("some.album"))) + .include(Field::TrackNumber(5)) + .include(Field::TrackArtist(vec![String::from("some.artist")])); let arguments = vec![ "ls".to_string(), LIST_FORMAT_ARG.to_string(), - String::from("^album:some.album"), String::from("track:5"), String::from("artist:some.artist"), + String::from("^album:some.album"), ]; let result = Ok(vec![]); @@ -513,7 +480,7 @@ mod tests { .return_once(|_| result); let mut beets = BeetsLibrary::new(executor); - let err = beets.list(&Query::default()).unwrap_err(); + let err = beets.list(&Query::new()).unwrap_err(); assert_eq!(err, Error::Invalid(invalid_string)); } @@ -544,7 +511,7 @@ mod tests { .return_once(|_| result); let mut beets = BeetsLibrary::new(executor); - let err = beets.list(&Query::default()).unwrap_err(); + let err = beets.list(&Query::new()).unwrap_err(); assert_eq!(err, Error::Invalid(invalid_string)); } diff --git a/src/library/mod.rs b/src/library/mod.rs index cd79093..381f273 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -9,81 +9,50 @@ use crate::Artist; pub mod beets; -/// A single query option. +/// Individual fields that can be queried on. #[derive(Debug, PartialEq, Eq)] -pub enum QueryOption { - /// Inclusive query. - Include(T), - /// Exclusive query. - Exclude(T), - /// No query. - None, +pub enum Field { + AlbumArtist(String), + AlbumYear(u32), + AlbumTitle(String), + TrackNumber(u32), + TrackTitle(String), + TrackArtist(Vec), + All(String), } -impl Default for QueryOption { - /// Create a [`QueryOption::None`] for type `T`. - fn default() -> Self { - Self::None - } -} - -/// Options for refining library queries. -#[derive(Debug, Default, PartialEq, Eq)] +/// A library query. Can include or exclude particular fields. +#[derive(Debug, PartialEq, Eq)] pub struct Query { - album_artist: QueryOption, - album_year: QueryOption, - album_title: QueryOption, - track_number: QueryOption, - track_title: QueryOption, - track_artist: QueryOption>, - all: QueryOption, + include: Vec, + exclude: Vec, +} + +impl Default for Query { + /// Create an empty query. + fn default() -> Self { + Self::new() + } } impl Query { /// Create an empty query. pub fn new() -> Self { - Query::default() + Query { + include: vec![], + exclude: vec![], + } } - /// Refine the query to a specific album artist. - pub fn album_artist(mut self, album_artist: QueryOption) -> Self { - self.album_artist = album_artist; + /// Refine the query to include a particular search term. + pub fn include(&mut self, field: Field) -> &mut Self { + self.include.push(field); self } - /// Refine the query to a specific album year. - pub fn album_year(mut self, album_year: QueryOption) -> Self { - self.album_year = album_year; - self - } - - /// Refine the query to a specific album title. - pub fn album_title(mut self, album_title: QueryOption) -> Self { - self.album_title = album_title; - self - } - - /// Refine the query to a specific track number. - pub fn track_number(mut self, track_number: QueryOption) -> Self { - self.track_number = track_number; - self - } - - /// Refine the query to a specific track title. - pub fn track_title(mut self, track_title: QueryOption) -> Self { - self.track_title = track_title; - self - } - - /// Refine the query to a specific set of track artists. - pub fn track_artist(mut self, track_artist: QueryOption>) -> Self { - self.track_artist = track_artist; - self - } - - /// Refine the query for all fields. - pub fn all(mut self, all: QueryOption) -> Self { - self.all = all; + /// Refine the query to exclude a particular search term. + pub fn exclude(&mut self, field: Field) -> &mut Self { + self.exclude.push(field); self } } @@ -144,7 +113,21 @@ pub trait Library { mod tests { use std::io; - use super::Error; + use super::{Error, Field, Query}; + + #[test] + fn query() { + let mut lhs = Query::new(); + let mut rhs = Query::new(); + assert_eq!( + lhs.include(Field::AlbumArtist(String::from("some.artist"))) + .exclude(Field::TrackTitle(String::from("some.title"))) + .include(Field::TrackNumber(6)), + rhs.exclude(Field::TrackTitle(String::from("some.title"))) + .include(Field::AlbumArtist(String::from("some.artist"))) + .include(Field::TrackNumber(6)), + ) + } #[test] fn errors() { diff --git a/tests/library/beets.rs b/tests/library/beets.rs index 289bfec..ac4d358 100644 --- a/tests/library/beets.rs +++ b/tests/library/beets.rs @@ -9,7 +9,7 @@ use once_cell::sync::Lazy; use musichoard::{ library::{ beets::{BeetsLibrary, BeetsLibraryCommandExecutor}, - Library, Query, QueryOption, + Field, Library, Query, }, Artist, }; @@ -37,7 +37,7 @@ fn test_no_config_list() { let beets_arc = BEETS_EMPTY_CONFIG.clone(); let beets = &mut beets_arc.lock().unwrap(); - let output = beets.list(&Query::default()).unwrap(); + let output = beets.list(&Query::new()).unwrap(); let expected: Vec = vec![]; assert_eq!(output, expected); @@ -49,7 +49,7 @@ fn test_invalid_config() { &PathBuf::from("./tests/files/library/config-does-not-exist.yml"), ))); - let result = beets.list(&Query::default()); + let result = beets.list(&Query::new()); assert!(result.is_err()); assert!(!result.unwrap_err().to_string().is_empty()); } @@ -59,7 +59,7 @@ fn test_full_list() { let beets_arc = BEETS_TEST_CONFIG.clone(); let beets = &mut beets_arc.lock().unwrap(); - let output = beets.list(&Query::default()).unwrap(); + let output = beets.list(&Query::new()).unwrap(); let expected: Vec = COLLECTION.to_owned(); assert_eq!(output, expected); @@ -71,7 +71,7 @@ fn test_album_artist_query() { let beets = &mut beets_arc.lock().unwrap(); let output = beets - .list(&Query::default().album_artist(QueryOption::Include(String::from("Аркона")))) + .list(Query::new().include(Field::AlbumArtist(String::from("Аркона")))) .unwrap(); let expected: Vec = COLLECTION[0..1].to_owned(); @@ -84,7 +84,7 @@ fn test_album_title_query() { let beets = &mut beets_arc.lock().unwrap(); let output = beets - .list(&Query::default().album_title(QueryOption::Include(String::from("Slovo")))) + .list(&Query::new().include(Field::AlbumTitle(String::from("Slovo")))) .unwrap(); let expected: Vec = COLLECTION[0..1].to_owned(); @@ -97,7 +97,7 @@ fn test_exclude_query() { let beets = &mut beets_arc.lock().unwrap(); let output = beets - .list(&Query::default().album_artist(QueryOption::Exclude(String::from("Аркона")))) + .list(&Query::new().exclude(Field::AlbumArtist(String::from("Аркона")))) .unwrap(); let expected: Vec = COLLECTION[1..].to_owned();