Clean up QueryOption

This commit is contained in:
Wojciech Kozlowski 2023-03-30 23:34:23 +09:00
parent 37f7a15dd0
commit d5572644ee
5 changed files with 142 additions and 94 deletions

View File

@ -59,21 +59,17 @@ impl DatabaseWrite for DatabaseJson {
mod tests { mod tests {
use std::path::Path; use std::path::Path;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use uuid::uuid;
use super::*; use super::*;
use crate::{Album, AlbumArtist, Track}; use crate::{Album, Track};
const TEST_FILENAME: &str = "tests/files/database_json_test.json"; const TEST_FILENAME: &str = "tests/files/database_json_test.json";
fn test_data() -> Vec<Album> { fn test_data() -> Vec<Album> {
vec![ vec![
Album { Album {
artist: AlbumArtist { artist: String::from("Artist A"),
name: String::from("Artist A"),
mbid: Some(uuid!("f7769831-746b-4a12-8124-0123d7fe17c9")),
},
year: 1998, year: 1998,
title: String::from("Release group A"), title: String::from("Release group A"),
tracks: vec![ tracks: vec![
@ -95,10 +91,7 @@ mod tests {
], ],
}, },
Album { Album {
artist: AlbumArtist { artist: String::from("Artist B"),
name: String::from("Artist B"),
mbid: None,
},
year: 2008, year: 2008,
title: String::from("Release group B"), title: String::from("Release group B"),
tracks: vec![Track { tracks: vec![Track {

View File

@ -9,15 +9,8 @@ pub mod library;
/// [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID). /// [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
pub type Mbid = Uuid; pub type Mbid = Uuid;
/// An album artist. Carries a MBID to facilitate discography access.
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct AlbumArtist {
pub name: String,
pub mbid: Option<Mbid>,
}
/// A single track on an album. /// A single track on an album.
#[derive(Debug, Deserialize, Serialize, PartialEq)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Track { pub struct Track {
pub number: u32, pub number: u32,
pub title: String, pub title: String,
@ -25,9 +18,9 @@ pub struct Track {
} }
/// An album is a collection of tracks that were released together. /// An album is a collection of tracks that were released together.
#[derive(Debug, Deserialize, Serialize, PartialEq)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Album { pub struct Album {
pub artist: AlbumArtist, pub artist: String,
pub year: u32, pub year: u32,
pub title: String, pub title: String,
pub tracks: Vec<Track>, pub tracks: Vec<Track>,

View File

@ -1,13 +1,32 @@
use std::{collections::HashSet, fmt::Display, process::Command}; use std::{collections::HashSet, fmt::Display, process::Command};
use crate::{Album, AlbumArtist, Track}; use crate::{Album, Track};
use super::{Error, Library, Query, QueryValue}; use super::{Error, Library, Query, QueryOption};
impl<T: Display> QueryValue<T> { pub trait SimpleOption {}
fn to_string(&self, option_name: &str) -> String { impl SimpleOption for String {}
let negate = if self.negate { "^" } else { "" }; impl SimpleOption for u32 {}
format!("{}{}:{}", negate, option_name, self.value)
impl<T: SimpleOption + Display> QueryOption<T> {
fn to_arg(&self, option_name: &str) -> Option<String> {
let (negate, value) = match self {
Self::Include(value) => ("", value),
Self::Exclude(value) => ("^", value),
Self::None => return None,
};
Some(format!("{}{}:{}", negate, option_name, value))
}
}
impl QueryOption<Vec<String>> {
fn to_arg(&self, option_name: &str) -> Option<String> {
let (negate, vec) = match self {
Self::Include(value) => ("", value),
Self::Exclude(value) => ("^", value),
Self::None => return None,
};
Some(format!("{}{}:{}", negate, option_name, vec.join("; ")))
} }
} }
@ -15,24 +34,28 @@ impl Query {
fn to_args(&self) -> Vec<String> { fn to_args(&self) -> Vec<String> {
let mut arguments: Vec<String> = vec![]; let mut arguments: Vec<String> = vec![];
if let Some(ref albumartist) = self.albumartist { if let Some(album_artist) = self.album_artist.to_arg("albumartist") {
arguments.push(albumartist.to_string("albumartist")); arguments.push(album_artist);
}; };
if let Some(ref album) = self.album { if let Some(album_year) = self.album_year.to_arg("year") {
arguments.push(album.to_string("album")); arguments.push(album_year);
}; };
if let Some(ref track) = self.track { if let Some(album_title) = self.album_title.to_arg("album") {
arguments.push(track.to_string("track")); arguments.push(album_title);
}; };
if let Some(ref title) = self.title { if let Some(track_number) = self.track_number.to_arg("track") {
arguments.push(title.to_string("title")); arguments.push(track_number);
}; };
if let Some(ref artist) = self.artist { if let Some(track_title) = self.track_title.to_arg("title") {
arguments.push(artist.to_string("artist")); arguments.push(track_title);
};
if let Some(track_artist) = self.track_artist.to_arg("artist") {
arguments.push(track_artist);
}; };
arguments arguments
@ -82,9 +105,7 @@ struct AlbumId {
impl AlbumId { impl AlbumId {
fn matches(&self, album: &Album) -> bool { fn matches(&self, album: &Album) -> bool {
(self.artist == album.artist.name) (self.artist == album.artist) && (self.year == album.year) && (self.title == album.title)
&& (self.year == album.year)
&& (self.title == album.title)
} }
} }
@ -150,10 +171,7 @@ impl Beets {
let album = albums.iter_mut().rev().find(|a| aid.matches(a)).unwrap(); let album = albums.iter_mut().rev().find(|a| aid.matches(a)).unwrap();
album.tracks.push(track); album.tracks.push(track);
} else { } else {
let album_artist = AlbumArtist { let album_artist = aid.artist.to_string();
name: aid.artist.to_string(),
mbid: None,
};
let album_title = aid.title.to_string(); let album_title = aid.title.to_string();
album_ids.insert(aid); album_ids.insert(aid);
albums.push(Album { albums.push(Album {
@ -201,10 +219,7 @@ mod tests {
fn test_data() -> Vec<Album> { fn test_data() -> Vec<Album> {
vec![ vec![
Album { Album {
artist: AlbumArtist { artist: "album_artist.a".to_string(),
name: "album_artist.a".to_string(),
mbid: None,
},
year: 1998, year: 1998,
title: "album_title.a".to_string(), title: "album_title.a".to_string(),
tracks: vec![ tracks: vec![
@ -226,10 +241,7 @@ mod tests {
], ],
}, },
Album { Album {
artist: AlbumArtist { artist: "album_artist.b".to_string(),
name: "album_artist.b".to_string(),
mbid: None,
},
year: 2003, year: 2003,
title: "album_title.b".to_string(), title: "album_title.b".to_string(),
tracks: vec![ tracks: vec![
@ -249,7 +261,7 @@ mod tests {
} }
fn album_to_beets_string(album: &Album) -> Vec<String> { fn album_to_beets_string(album: &Album) -> Vec<String> {
let album_artist = &album.artist.name; let album_artist = &album.artist;
let album_year = &album.year; let album_year = &album.year;
let album_title = &album.title; let album_title = &album.title;
@ -271,29 +283,20 @@ mod tests {
#[test] #[test]
fn test_query() { fn test_query() {
let query = Query { let query = Query::new()
albumartist: None, .album_title(QueryOption::exclude(String::from("some.album")))
album: Some(QueryValue { .track_number(QueryOption::include(5))
negate: true, .track_artist(QueryOption::include(vec![
value: String::from("some.album"), String::from("some.artist.1"),
}), String::from("some.artist.2"),
track: Some(QueryValue { ]));
negate: false,
value: 5,
}),
title: None,
artist: Some(QueryValue {
negate: false,
value: String::from("some.artist"),
}),
};
assert_eq!( assert_eq!(
query.to_args(), query.to_args(),
vec![ vec![
String::from("^album:some.album"), String::from("^album:some.album"),
String::from("track:5"), String::from("track:5"),
String::from("artist:some.artist") String::from("artist:some.artist.1; some.artist.2")
] ]
); );
} }
@ -381,22 +384,10 @@ mod tests {
#[test] #[test]
fn test_list_query() { fn test_list_query() {
let query = Query { let query = Query::new()
albumartist: None, .album_title(QueryOption::exclude(String::from("some.album")))
album: Some(QueryValue { .track_number(QueryOption::include(5))
negate: true, .track_artist(QueryOption::include(vec![String::from("some.artist")]));
value: String::from("some.album"),
}),
track: Some(QueryValue {
negate: false,
value: 5,
}),
title: None,
artist: Some(QueryValue {
negate: false,
value: String::from("some.artist"),
}),
};
let executor = TestExecutor { let executor = TestExecutor {
arguments: Some(vec![ arguments: Some(vec![

View File

@ -4,19 +4,90 @@ use crate::Album;
pub mod beets; pub mod beets;
pub struct QueryValue<T> { /// A single query option.
negate: bool, pub enum QueryOption<T> {
value: T, /// Inclusive query.
Include(T),
/// Exclusive query.
Exclude(T),
/// No query.
None,
}
impl<T> QueryOption<T> {
/// Create an inclusive query option.
pub fn include(value: T) -> Self {
QueryOption::Include(value)
}
pub fn exclude(value: T) -> Self {
QueryOption::Exclude(value)
}
pub fn none() -> Self {
QueryOption::None
}
pub fn is_some(&self) -> bool {
!matches!(self, QueryOption::None)
}
pub fn is_none(&self) -> bool {
matches!(self, QueryOption::None)
}
}
impl<T> Default for QueryOption<T> {
fn default() -> Self {
Self::none()
}
} }
/// Options for refining library queries. /// Options for refining library queries.
#[derive(Default)] #[derive(Default)]
pub struct Query { pub struct Query {
albumartist: Option<QueryValue<String>>, album_artist: QueryOption<String>,
album: Option<QueryValue<String>>, album_year: QueryOption<u32>,
track: Option<QueryValue<u32>>, album_title: QueryOption<String>,
title: Option<QueryValue<String>>, track_number: QueryOption<u32>,
artist: Option<QueryValue<String>>, track_title: QueryOption<String>,
track_artist: QueryOption<Vec<String>>,
}
impl Query {
pub fn new() -> Self {
Query::default()
}
pub fn album_artist(mut self, album_artist: QueryOption<String>) -> Self {
self.album_artist = album_artist;
self
}
pub fn album_year(mut self, album_year: QueryOption<u32>) -> Self {
self.album_year = album_year;
self
}
pub fn album_title(mut self, album_title: QueryOption<String>) -> Self {
self.album_title = album_title;
self
}
pub fn track_number(mut self, track_number: QueryOption<u32>) -> Self {
self.track_number = track_number;
self
}
pub fn track_title(mut self, track_title: QueryOption<String>) -> Self {
self.track_title = track_title;
self
}
pub fn track_artist(mut self, track_artist: QueryOption<Vec<String>>) -> Self {
self.track_artist = track_artist;
self
}
} }
/// Error type for library calls. /// Error type for library calls.

View File

@ -1 +1 @@
[{"artist":{"name":"Artist A","mbid":"f7769831-746b-4a12-8124-0123d7fe17c9"},"year":1998,"title":"Release group A","tracks":[{"number":1,"title":"Track A.1","artist":["Artist A.A"]},{"number":2,"title":"Track A.2","artist":["Artist A.A"]},{"number":3,"title":"Track A.3","artist":["Artist A.A","Artist A.B"]}]},{"artist":{"name":"Artist B","mbid":null},"year":2008,"title":"Release group B","tracks":[{"number":1,"title":"Track B.1","artist":["Artist B.A"]}]}] [{"artist":"Artist A","year":1998,"title":"Release group A","tracks":[{"number":1,"title":"Track A.1","artist":["Artist A.A"]},{"number":2,"title":"Track A.2","artist":["Artist A.A"]},{"number":3,"title":"Track A.3","artist":["Artist A.A","Artist A.B"]}]},{"artist":"Artist B","year":2008,"title":"Release group B","tracks":[{"number":1,"title":"Track B.1","artist":["Artist B.A"]}]}]