Create the main binary (#15)

Closes #7

Reviewed-on: https://git.wojciechkozlowski.eu/wojtek/musichoard/pulls/15
This commit is contained in:
Wojciech Kozlowski 2023-04-10 00:13:18 +02:00
parent 5c7579ba23
commit 50f4d00e31
14 changed files with 885 additions and 31 deletions

191
Cargo.lock generated
View File

@ -2,6 +2,26 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -20,6 +40,21 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "errno"
version = "0.3.0"
@ -50,6 +85,24 @@ dependencies = [
"instant",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
@ -71,7 +124,7 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
dependencies = [
"hermit-abi",
"hermit-abi 0.3.1",
"libc",
"windows-sys",
]
@ -82,6 +135,12 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.140"
@ -98,12 +157,44 @@ checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d"
name = "musichoard"
version = "0.1.0"
dependencies = [
"once_cell",
"serde",
"serde_json",
"structopt",
"tempfile",
"uuid",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.54"
@ -168,7 +259,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.11",
]
[[package]]
@ -182,6 +273,47 @@ dependencies = [
"serde",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.11"
@ -206,12 +338,33 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "uuid"
version = "1.3.0"
@ -221,6 +374,40 @@ dependencies = [
"serde",
]
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -8,7 +8,9 @@ edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
structopt = "0.3"
uuid = { version = "1.3", features = ["serde"] }
[dev-dependencies]
once_cell = "1.17"
tempfile = "3.5"

View File

@ -1,7 +1,7 @@
//! Module for storing MusicHoard data in a JSON file database.
use std::fs::{read_to_string, write};
use std::path::PathBuf;
use std::fs;
use std::path::{Path, PathBuf};
use serde::de::DeserializeOwned;
use serde::Serialize;
@ -56,8 +56,10 @@ pub struct DatabaseJsonFile {
impl DatabaseJsonFile {
/// Create a database instance that will read/write to the provided path.
pub fn new(path: PathBuf) -> Self {
DatabaseJsonFile { path }
pub fn new(path: &Path) -> Self {
DatabaseJsonFile {
path: path.to_path_buf(),
}
}
}
@ -65,11 +67,11 @@ impl DatabaseJsonBackend for DatabaseJsonFile {
fn read(&self) -> Result<String, std::io::Error> {
// Read entire file to memory as for now this is faster than a buffered read from disk:
// https://github.com/serde-rs/json/issues/160
read_to_string(&self.path)
fs::read_to_string(&self.path)
}
fn write(&mut self, json: &str) -> Result<(), std::io::Error> {
write(&self.path, json)
fs::write(&self.path, json)
}
}
@ -77,7 +79,7 @@ impl DatabaseJsonBackend for DatabaseJsonFile {
mod tests {
use super::*;
use crate::{tests::test_data, Artist};
use crate::{tests::COLLECTION, Artist};
struct DatabaseJsonTest {
json: String,
@ -154,7 +156,7 @@ mod tests {
#[test]
fn write() {
let write_data = test_data();
let write_data = COLLECTION.to_owned();
let backend = DatabaseJsonTest {
json: artists_to_json(&write_data),
};
@ -166,7 +168,7 @@ mod tests {
#[test]
fn read() {
let expected = test_data();
let expected = COLLECTION.to_owned();
let backend = DatabaseJsonTest {
json: artists_to_json(&expected),
};
@ -181,13 +183,13 @@ mod tests {
#[test]
fn reverse() {
let expected = test_data();
let expected = COLLECTION.to_owned();
let backend = DatabaseJsonTest {
json: artists_to_json(&expected),
};
let mut database = DatabaseJson::new(Box::new(backend));
let write_data = test_data();
let write_data = COLLECTION.to_owned();
let mut read_data: Vec<Artist> = vec![];
database.write(&write_data).unwrap();
database.read(&mut read_data).unwrap();

View File

@ -10,7 +10,7 @@ pub mod library;
pub type Mbid = Uuid;
/// A single track on an album.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Track {
pub number: u32,
pub title: String,
@ -18,27 +18,27 @@ pub struct Track {
}
/// The album identifier.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Hash)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct AlbumId {
pub year: u32,
pub title: String,
}
/// An album is a collection of tracks that were released together.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Album {
pub id: AlbumId,
pub tracks: Vec<Track>,
}
/// The artist identifier.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Hash)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct ArtistId {
pub name: String,
}
/// An artist.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Artist {
pub id: ArtistId,
pub albums: Vec<Album>,
@ -48,7 +48,9 @@ pub struct Artist {
mod tests {
use super::*;
pub fn test_data() -> Vec<Artist> {
use once_cell::sync::Lazy;
pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| {
vec![
Artist {
id: ArtistId {
@ -128,5 +130,5 @@ mod tests {
}],
},
]
}
});
}

View File

@ -4,7 +4,9 @@
use std::{
collections::{HashMap, HashSet},
fmt::Display,
path::{Path, PathBuf},
process::Command,
str,
};
use crate::{Album, AlbumId, Artist, ArtistId, Track};
@ -30,7 +32,7 @@ impl<T: SimpleOption + Display> QueryOptionArgBeets for QueryOption<T> {
Self::Exclude(value) => ("^", value),
Self::None => return None,
};
Some(format!("{}{}{}", negate, option_name, value))
Some(format!("{negate}{option_name}{value}"))
}
}
@ -41,7 +43,7 @@ impl QueryOptionArgBeets for QueryOption<Vec<String>> {
Self::Exclude(value) => ("^", value),
Self::None => return None,
};
Some(format!("{}{}{}", negate, option_name, vec.join("; ")))
Some(format!("{negate}{option_name}{}", vec.join("; ")))
}
}
@ -117,7 +119,7 @@ impl Library for Beets {
macro_rules! list_format_separator {
() => {
"-*^-"
" -*^- "
};
}
@ -151,8 +153,11 @@ impl LibraryPrivate for Beets {
let mut album_ids = HashMap::<ArtistId, HashSet<AlbumId>>::new();
for line in list_output.iter() {
let split: Vec<&str> = line.split(Self::LIST_FORMAT_SEPARATOR).collect();
if line.is_empty() {
continue;
}
let split: Vec<&str> = line.split(Self::LIST_FORMAT_SEPARATOR).collect();
if split.len() != 6 {
return Err(Error::InvalidData(line.to_string()));
}
@ -221,14 +226,21 @@ impl LibraryPrivate for Beets {
/// Executor for executing beets commands on the local system.
pub struct SystemExecutor {
bin: String,
config: Option<PathBuf>,
}
impl SystemExecutor {
pub fn new(bin: &str) -> SystemExecutor {
pub fn new(bin: &str) -> Self {
SystemExecutor {
bin: bin.to_string(),
config: None,
}
}
pub fn config(mut self, path: Option<&Path>) -> Self {
self.config = path.map(|p| p.to_path_buf());
self
}
}
impl Default for SystemExecutor {
@ -239,8 +251,18 @@ impl Default for SystemExecutor {
impl BeetsExecutor for SystemExecutor {
fn exec(&mut self, arguments: &[String]) -> Result<Vec<String>, Error> {
let output = Command::new(&self.bin).args(arguments).output()?;
let output = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::new(&self.bin);
if let Some(ref path) = self.config {
cmd.arg("--config");
cmd.arg(path);
}
let output = cmd.args(arguments).output()?;
if !output.status.success() {
return Err(Error::CmdExecError(
String::from_utf8_lossy(&output.stderr).to_string(),
));
}
let output = str::from_utf8(&output.stdout)?;
Ok(output.split('\n').map(|s| s.to_string()).collect())
}
}
@ -249,7 +271,7 @@ impl BeetsExecutor for SystemExecutor {
mod tests {
use super::*;
use crate::tests::test_data;
use crate::tests::COLLECTION;
struct TestExecutor {
arguments: Option<Vec<String>>,
@ -333,7 +355,7 @@ mod tests {
#[test]
fn test_list_ordered() {
let expected = test_data();
let expected = COLLECTION.to_owned();
let output = artists_to_beets_string(&expected);
let executor = TestExecutor {
@ -348,7 +370,7 @@ mod tests {
#[test]
fn test_list_unordered() {
let mut expected = test_data();
let mut expected = COLLECTION.to_owned();
let mut output = artists_to_beets_string(&expected);
let last = output.len() - 1;
output.swap(0, last);
@ -377,7 +399,7 @@ mod tests {
#[test]
fn test_list_album_title_year_clash() {
let mut expected = test_data();
let mut expected = COLLECTION.to_owned();
expected[0].albums[0].id.year = expected[1].albums[0].id.year;
expected[0].albums[0].id.title = expected[1].albums[0].id.title.clone();

View File

@ -98,6 +98,8 @@ impl Query {
/// Error type for library calls.
#[derive(Debug)]
pub enum Error {
/// The underlying library failed to execute a command.
CmdExecError(String),
/// The underlying library returned invalid data.
InvalidData(String),
/// The underlying library experienced an I/O error.

52
src/main.rs Normal file
View File

@ -0,0 +1,52 @@
use std::path::PathBuf;
use structopt::StructOpt;
use musichoard::{
database::{
json::{DatabaseJson, DatabaseJsonFile},
DatabaseWrite,
},
library::{
beets::{Beets, SystemExecutor},
Library, Query,
},
};
#[derive(StructOpt)]
struct Opt {
#[structopt(
short = "b",
long = "beets-config",
name = "beets config file path",
parse(from_os_str)
)]
beets_config_file_path: Option<PathBuf>,
#[structopt(
short = "d",
long = "database",
name = "database file path",
default_value = "database.json",
parse(from_os_str)
)]
database_file_path: PathBuf,
}
fn main() {
let opt = Opt::from_args();
let mut beets = Beets::new(Box::new(
SystemExecutor::default().config(opt.beets_config_file_path.as_deref()),
));
let collection = beets
.list(&Query::new())
.expect("failed to query the library");
let mut database = DatabaseJson::new(Box::new(DatabaseJsonFile::new(&opt.database_file_path)));
database
.write(&collection)
.expect("failed to write to the database");
}

59
tests/database/json.rs Normal file
View File

@ -0,0 +1,59 @@
use std::fs;
use musichoard::{
database::{
json::{DatabaseJson, DatabaseJsonFile},
DatabaseRead, DatabaseWrite,
},
Artist,
};
use tempfile::NamedTempFile;
use crate::COLLECTION;
#[test]
fn write() {
let file = NamedTempFile::new().unwrap();
let backend = DatabaseJsonFile::new(file.path());
let mut database = DatabaseJson::new(Box::new(backend));
let write_data = COLLECTION.to_owned();
database.write(&write_data).unwrap();
let expected_path = fs::canonicalize("./tests/files/database/database.json").unwrap();
let expected = fs::read_to_string(expected_path).unwrap();
let actual = fs::read_to_string(file.path()).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn read() {
let file_path = fs::canonicalize("./tests/files/database/database.json").unwrap();
let backend = DatabaseJsonFile::new(&file_path);
let database = DatabaseJson::new(Box::new(backend));
let mut read_data: Vec<Artist> = vec![];
database.read(&mut read_data).unwrap();
let expected = COLLECTION.to_owned();
assert_eq!(read_data, expected);
}
#[test]
fn reverse() {
let file = NamedTempFile::new().unwrap();
let backend = DatabaseJsonFile::new(file.path());
let mut database = DatabaseJson::new(Box::new(backend));
let write_data = COLLECTION.to_owned();
database.write(&write_data).unwrap();
let mut read_data: Vec<Artist> = vec![];
database.read(&mut read_data).unwrap();
assert_eq!(write_data, read_data);
}

1
tests/database/mod.rs Normal file
View File

@ -0,0 +1 @@
mod json;

1
tests/files/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
library

View File

@ -0,0 +1 @@
[{"id":{"name":"Аркона"},"albums":[{"id":{"year":2011,"title":"Slovo"},"tracks":[{"number":1,"title":"Az","artist":["Аркона"]},{"number":2,"title":"Arkaim","artist":["Аркона"]},{"number":3,"title":"Bolno mne","artist":["Аркона"]},{"number":4,"title":"Leshiy","artist":["Аркона"]},{"number":5,"title":"Zakliatie","artist":["Аркона"]},{"number":6,"title":"Predok","artist":["Аркона"]},{"number":7,"title":"Nikogda","artist":["Аркона"]},{"number":8,"title":"Tam za tumanami","artist":["Аркона"]},{"number":9,"title":"Potomok","artist":["Аркона"]},{"number":10,"title":"Slovo","artist":["Аркона"]},{"number":11,"title":"Odna","artist":["Аркона"]},{"number":12,"title":"Vo moiom sadochke…","artist":["Аркона"]},{"number":13,"title":"Stenka na stenku","artist":["Аркона"]},{"number":14,"title":"Zimushka","artist":["Аркона"]}]}]},{"id":{"name":"Eluveitie"},"albums":[{"id":{"year":2008,"title":"Slania"},"tracks":[{"number":1,"title":"Samon","artist":["Eluveitie"]},{"number":2,"title":"Primordial Breath","artist":["Eluveitie"]},{"number":3,"title":"Inis Mona","artist":["Eluveitie"]},{"number":4,"title":"Gray Sublime Archon","artist":["Eluveitie"]},{"number":5,"title":"Anagantios","artist":["Eluveitie"]},{"number":6,"title":"Bloodstained Ground","artist":["Eluveitie"]},{"number":7,"title":"The Somber Lay","artist":["Eluveitie"]},{"number":8,"title":"Slanias Song","artist":["Eluveitie"]},{"number":9,"title":"Giamonios","artist":["Eluveitie"]},{"number":10,"title":"Tarvos","artist":["Eluveitie"]},{"number":11,"title":"Calling the Rain","artist":["Eluveitie"]},{"number":12,"title":"Elembivos","artist":["Eluveitie"]}]},{"id":{"year":2004,"title":"Vên [rerecorded]"},"tracks":[{"number":1,"title":"Verja Urit an Bitus","artist":["Eluveitie"]},{"number":2,"title":"Uis Elveti","artist":["Eluveitie"]},{"number":3,"title":"Ôrô","artist":["Eluveitie"]},{"number":4,"title":"Lament","artist":["Eluveitie"]},{"number":5,"title":"Druid","artist":["Eluveitie"]},{"number":6,"title":"Jêzaïg","artist":["Eluveitie"]}]}]},{"id":{"name":"Frontside"},"albums":[{"id":{"year":2001,"title":"…nasze jest królestwo, potęga i chwała na wieki…"},"tracks":[{"number":1,"title":"Intro = Chaos","artist":["Frontside"]},{"number":2,"title":"Modlitwa","artist":["Frontside"]},{"number":3,"title":"Długa droga z piekła","artist":["Frontside"]},{"number":4,"title":"Synowie ognia","artist":["Frontside"]},{"number":5,"title":"1902","artist":["Frontside"]},{"number":6,"title":"Krew za krew","artist":["Frontside"]},{"number":7,"title":"Kulminacja","artist":["Frontside"]},{"number":8,"title":"Judasz","artist":["Frontside"]},{"number":9,"title":"Więzy","artist":["Frontside"]},{"number":10,"title":"Zagubione dusze","artist":["Frontside"]},{"number":11,"title":"Linia życia","artist":["Frontside"]}]}]},{"id":{"name":"Metallica"},"albums":[{"id":{"year":1984,"title":"Ride the Lightning"},"tracks":[{"number":1,"title":"Fight Fire with Fire","artist":["Metallica"]},{"number":2,"title":"Ride the Lightning","artist":["Metallica"]},{"number":3,"title":"For Whom the Bell Tolls","artist":["Metallica"]},{"number":4,"title":"Fade to Black","artist":["Metallica"]},{"number":5,"title":"Trapped under Ice","artist":["Metallica"]},{"number":6,"title":"Escape","artist":["Metallica"]},{"number":7,"title":"Creeping Death","artist":["Metallica"]},{"number":8,"title":"The Call of Ktulu","artist":["Metallica"]}]},{"id":{"year":1999,"title":"S&M"},"tracks":[{"number":1,"title":"The Ecstasy of Gold","artist":["Metallica"]},{"number":2,"title":"The Call of Ktulu","artist":["Metallica"]},{"number":3,"title":"Master of Puppets","artist":["Metallica"]},{"number":4,"title":"Of Wolf and Man","artist":["Metallica"]},{"number":5,"title":"The Thing That Should Not Be","artist":["Metallica"]},{"number":6,"title":"Fuel","artist":["Metallica"]},{"number":7,"title":"The Memory Remains","artist":["Metallica"]},{"number":8,"title":"No Leaf Clover","artist":["Metallica"]},{"number":9,"title":"Hero of the Day","artist":["Metallica"]},{"number":10,"title":"Devils Dance","artist":["Metallica"]},{"number":11,"title":"Bleeding Me","artist":["Metallica"]},{"number":12,"title":"Nothing Else Matters","artist":["Metallica"]},{"number":13,"title":"Until It Sleeps","artist":["Metallica"]},{"number":14,"title":"For Whom the Bell Tolls","artist":["Metallica"]},{"number":15,"title":"Human","artist":["Metallica"]},{"number":16,"title":"Wherever I May Roam","artist":["Metallica"]},{"number":17,"title":"Outlaw Torn","artist":["Metallica"]},{"number":18,"title":"Sad but True","artist":["Metallica"]},{"number":19,"title":"One","artist":["Metallica"]},{"number":20,"title":"Enter Sandman","artist":["Metallica"]},{"number":21,"title":"Battery","artist":["Metallica"]}]}]}]

442
tests/lib.rs Normal file
View File

@ -0,0 +1,442 @@
mod database;
mod library;
use musichoard::{Album, AlbumId, Artist, ArtistId, Track};
use once_cell::sync::Lazy;
static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| {
vec![
Artist {
id: ArtistId {
name: String::from("Аркона"),
},
albums: vec![Album {
id: AlbumId {
year: 2011,
title: String::from("Slovo"),
},
tracks: vec![
Track {
number: 01,
title: String::from("Az"),
artist: vec![String::from("Аркона")],
},
Track {
number: 02,
title: String::from("Arkaim"),
artist: vec![String::from("Аркона")],
},
Track {
number: 03,
title: String::from("Bolno mne"),
artist: vec![String::from("Аркона")],
},
Track {
number: 04,
title: String::from("Leshiy"),
artist: vec![String::from("Аркона")],
},
Track {
number: 05,
title: String::from("Zakliatie"),
artist: vec![String::from("Аркона")],
},
Track {
number: 06,
title: String::from("Predok"),
artist: vec![String::from("Аркона")],
},
Track {
number: 07,
title: String::from("Nikogda"),
artist: vec![String::from("Аркона")],
},
Track {
number: 08,
title: String::from("Tam za tumanami"),
artist: vec![String::from("Аркона")],
},
Track {
number: 09,
title: String::from("Potomok"),
artist: vec![String::from("Аркона")],
},
Track {
number: 10,
title: String::from("Slovo"),
artist: vec![String::from("Аркона")],
},
Track {
number: 11,
title: String::from("Odna"),
artist: vec![String::from("Аркона")],
},
Track {
number: 12,
title: String::from("Vo moiom sadochke…"),
artist: vec![String::from("Аркона")],
},
Track {
number: 13,
title: String::from("Stenka na stenku"),
artist: vec![String::from("Аркона")],
},
Track {
number: 14,
title: String::from("Zimushka"),
artist: vec![String::from("Аркона")],
},
],
}],
},
Artist {
id: ArtistId {
name: String::from("Eluveitie"),
},
albums: vec![
Album {
id: AlbumId {
year: 2008,
title: String::from("Slania"),
},
tracks: vec![
Track {
number: 01,
title: String::from("Samon"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 02,
title: String::from("Primordial Breath"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 03,
title: String::from("Inis Mona"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 04,
title: String::from("Gray Sublime Archon"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 05,
title: String::from("Anagantios"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 06,
title: String::from("Bloodstained Ground"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 07,
title: String::from("The Somber Lay"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 08,
title: String::from("Slanias Song"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 09,
title: String::from("Giamonios"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 10,
title: String::from("Tarvos"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 11,
title: String::from("Calling the Rain"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 12,
title: String::from("Elembivos"),
artist: vec![String::from("Eluveitie")],
},
],
},
Album {
id: AlbumId {
year: 2004,
title: String::from("Vên [rerecorded]"),
},
tracks: vec![
Track {
number: 01,
title: String::from("Verja Urit an Bitus"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 02,
title: String::from("Uis Elveti"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 03,
title: String::from("Ôrô"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 04,
title: String::from("Lament"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 05,
title: String::from("Druid"),
artist: vec![String::from("Eluveitie")],
},
Track {
number: 06,
title: String::from("Jêzaïg"),
artist: vec![String::from("Eluveitie")],
},
],
},
],
},
Artist {
id: ArtistId {
name: String::from("Frontside"),
},
albums: vec![Album {
id: AlbumId {
year: 2001,
title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
},
tracks: vec![
Track {
number: 01,
title: String::from("Intro = Chaos"),
artist: vec![String::from("Frontside")],
},
Track {
number: 02,
title: String::from("Modlitwa"),
artist: vec![String::from("Frontside")],
},
Track {
number: 03,
title: String::from("Długa droga z piekła"),
artist: vec![String::from("Frontside")],
},
Track {
number: 04,
title: String::from("Synowie ognia"),
artist: vec![String::from("Frontside")],
},
Track {
number: 05,
title: String::from("1902"),
artist: vec![String::from("Frontside")],
},
Track {
number: 06,
title: String::from("Krew za krew"),
artist: vec![String::from("Frontside")],
},
Track {
number: 07,
title: String::from("Kulminacja"),
artist: vec![String::from("Frontside")],
},
Track {
number: 08,
title: String::from("Judasz"),
artist: vec![String::from("Frontside")],
},
Track {
number: 09,
title: String::from("Więzy"),
artist: vec![String::from("Frontside")],
},
Track {
number: 10,
title: String::from("Zagubione dusze"),
artist: vec![String::from("Frontside")],
},
Track {
number: 11,
title: String::from("Linia życia"),
artist: vec![String::from("Frontside")],
},
],
}],
},
Artist {
id: ArtistId {
name: String::from("Metallica"),
},
albums: vec![
Album {
id: AlbumId {
year: 1984,
title: String::from("Ride the Lightning"),
},
tracks: vec![
Track {
number: 01,
title: String::from("Fight Fire with Fire"),
artist: vec![String::from("Metallica")],
},
Track {
number: 02,
title: String::from("Ride the Lightning"),
artist: vec![String::from("Metallica")],
},
Track {
number: 03,
title: String::from("For Whom the Bell Tolls"),
artist: vec![String::from("Metallica")],
},
Track {
number: 04,
title: String::from("Fade to Black"),
artist: vec![String::from("Metallica")],
},
Track {
number: 05,
title: String::from("Trapped under Ice"),
artist: vec![String::from("Metallica")],
},
Track {
number: 06,
title: String::from("Escape"),
artist: vec![String::from("Metallica")],
},
Track {
number: 07,
title: String::from("Creeping Death"),
artist: vec![String::from("Metallica")],
},
Track {
number: 08,
title: String::from("The Call of Ktulu"),
artist: vec![String::from("Metallica")],
},
],
},
Album {
id: AlbumId {
year: 1999,
title: String::from("S&M"),
},
tracks: vec![
Track {
number: 01,
title: String::from("The Ecstasy of Gold"),
artist: vec![String::from("Metallica")],
},
Track {
number: 02,
title: String::from("The Call of Ktulu"),
artist: vec![String::from("Metallica")],
},
Track {
number: 03,
title: String::from("Master of Puppets"),
artist: vec![String::from("Metallica")],
},
Track {
number: 04,
title: String::from("Of Wolf and Man"),
artist: vec![String::from("Metallica")],
},
Track {
number: 05,
title: String::from("The Thing That Should Not Be"),
artist: vec![String::from("Metallica")],
},
Track {
number: 06,
title: String::from("Fuel"),
artist: vec![String::from("Metallica")],
},
Track {
number: 07,
title: String::from("The Memory Remains"),
artist: vec![String::from("Metallica")],
},
Track {
number: 08,
title: String::from("No Leaf Clover"),
artist: vec![String::from("Metallica")],
},
Track {
number: 09,
title: String::from("Hero of the Day"),
artist: vec![String::from("Metallica")],
},
Track {
number: 10,
title: String::from("Devils Dance"),
artist: vec![String::from("Metallica")],
},
Track {
number: 11,
title: String::from("Bleeding Me"),
artist: vec![String::from("Metallica")],
},
Track {
number: 12,
title: String::from("Nothing Else Matters"),
artist: vec![String::from("Metallica")],
},
Track {
number: 13,
title: String::from("Until It Sleeps"),
artist: vec![String::from("Metallica")],
},
Track {
number: 14,
title: String::from("For Whom the Bell Tolls"),
artist: vec![String::from("Metallica")],
},
Track {
number: 15,
title: String::from("Human"),
artist: vec![String::from("Metallica")],
},
Track {
number: 16,
title: String::from("Wherever I May Roam"),
artist: vec![String::from("Metallica")],
},
Track {
number: 17,
title: String::from("Outlaw Torn"),
artist: vec![String::from("Metallica")],
},
Track {
number: 18,
title: String::from("Sad but True"),
artist: vec![String::from("Metallica")],
},
Track {
number: 19,
title: String::from("One"),
artist: vec![String::from("Metallica")],
},
Track {
number: 20,
title: String::from("Enter Sandman"),
artist: vec![String::from("Metallica")],
},
Track {
number: 21,
title: String::from("Battery"),
artist: vec![String::from("Metallica")],
},
],
},
],
},
]
});

80
tests/library/beets.rs Normal file
View File

@ -0,0 +1,80 @@
use std::fs;
use musichoard::{
library::{
beets::{Beets, SystemExecutor},
Library, Query, QueryOption,
},
Artist,
};
use crate::COLLECTION;
#[test]
fn test_no_config_list() {
let executor = SystemExecutor::default();
let mut beets = Beets::new(Box::new(executor));
let output = beets.list(&Query::default()).unwrap();
let expected: Vec<Artist> = vec![];
assert_eq!(output, expected);
}
#[test]
fn test_full_list() {
let executor = SystemExecutor::default().config(Some(
&fs::canonicalize("./tests/files/library/config.yml").unwrap(),
));
let mut beets = Beets::new(Box::new(executor));
let output = beets.list(&Query::default()).unwrap();
let expected: Vec<Artist> = COLLECTION.to_owned();
assert_eq!(output, expected);
}
#[test]
fn test_album_artist_query() {
let executor = SystemExecutor::default().config(Some(
&fs::canonicalize("./tests/files/library/config.yml").unwrap(),
));
let mut beets = Beets::new(Box::new(executor));
let output = beets
.list(&Query::default().album_artist(QueryOption::Include(String::from("Аркона"))))
.unwrap();
let expected: Vec<Artist> = COLLECTION[0..1].to_owned();
assert_eq!(output, expected);
}
#[test]
fn test_album_title_query() {
let executor = SystemExecutor::default().config(Some(
&fs::canonicalize("./tests/files/library/config.yml").unwrap(),
));
let mut beets = Beets::new(Box::new(executor));
let output = beets
.list(&Query::default().album_title(QueryOption::Include(String::from("Slovo"))))
.unwrap();
let expected: Vec<Artist> = COLLECTION[0..1].to_owned();
assert_eq!(output, expected);
}
#[test]
fn test_exclude_query() {
let executor = SystemExecutor::default().config(Some(
&fs::canonicalize("./tests/files/library/config.yml").unwrap(),
));
let mut beets = Beets::new(Box::new(executor));
let output = beets
.list(&Query::default().album_artist(QueryOption::Exclude(String::from("Аркона"))))
.unwrap();
let expected: Vec<Artist> = COLLECTION[1..].to_owned();
assert_eq!(output, expected);
}

1
tests/library/mod.rs Normal file
View File

@ -0,0 +1 @@
mod beets;