Add method to manually add artist metadata (#85)
All checks were successful
Cargo CI / Build and Test (push) Successful in 1m2s
Cargo CI / Lint (push) Successful in 42s
Cargo CI / Build and Test (pull_request) Successful in 2m26s
Cargo CI / Lint (pull_request) Successful in 44s

Closes #55

Reviewed-on: #85
This commit is contained in:
Wojciech Kozlowski 2024-01-10 22:33:57 +01:00
parent 1bc612dc45
commit 36b82049f2
6 changed files with 1219 additions and 32 deletions

View File

@ -30,6 +30,7 @@ jobs:
--ignore-not-existing
--ignore "tests/*"
--ignore "src/main.rs"
--ignore "src/bin/mh-edit.rs"
--excl-start "GRCOV_EXCL_START|mod tests \{"
--excl-stop "GRCOV_EXCL_STOP"
--output-path ./target/debug/coverage/

7
Cargo.lock generated
View File

@ -369,6 +369,7 @@ dependencies = [
"mockall",
"once_cell",
"openssh",
"paste",
"ratatui",
"serde",
"serde_json",
@ -456,6 +457,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "percent-encoding"
version = "2.2.0"

View File

@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
crossterm = { version = "0.26.1", optional = true}
openssh = { version = "0.9.9", features = ["native-mux"], default-features = false, optional = true}
paste = { version = "1.0.14" }
ratatui = { version = "0.20.1", optional = true}
serde = { version = "1.0.159", features = ["derive"] }
serde_json = { version = "1.0.95", optional = true}
@ -23,14 +24,19 @@ tempfile = "3.5.0"
[features]
default = ["database-json", "library-beets"]
bin = ["structopt"]
database-json = ["serde_json"]
library-beets = []
ssh-library = ["openssh", "tokio"]
tui = ["structopt", "crossterm", "ratatui"]
tui = ["crossterm", "ratatui"]
[[bin]]
name = "musichoard"
required-features = ["database-json", "library-beets", "ssh-library", "tui"]
required-features = ["bin", "database-json", "library-beets", "ssh-library", "tui"]
[[bin]]
name = "mh-edit"
required-features = ["bin", "database-json"]
[package.metadata.docs.rs]
all-features = true

View File

@ -32,6 +32,7 @@ grcov codecov/debug/profraw \
--ignore-not-existing \
--ignore "tests/*" \
--ignore "src/main.rs" \
--ignore "src/bin/mh-edit.rs" \
--excl-start "GRCOV_EXCL_START|mod tests \{" \
--excl-stop "GRCOV_EXCL_STOP" \
--output-path ./codecov/debug/coverage/

179
src/bin/mh-edit.rs Normal file
View File

@ -0,0 +1,179 @@
use paste::paste;
use std::path::PathBuf;
use structopt::{clap::AppSettings, StructOpt};
use musichoard::{
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
library::NoLibrary,
ArtistId, MusicHoard,
};
type MH = MusicHoard<NoLibrary, JsonDatabase<JsonDatabaseFileBackend>>;
#[derive(StructOpt, Debug)]
#[structopt(about = "mh-edit: edit the MusicHoard database",
global_settings=&[AppSettings::DeriveDisplayOrder])]
struct Opt {
#[structopt(
long = "database",
name = "database file path",
default_value = "database.json"
)]
database_file_path: PathBuf,
#[structopt(subcommand)]
category: Category,
}
#[derive(StructOpt, Debug)]
enum Category {
#[structopt(about = "Edit artist information")]
Artist(ArtistCommand),
}
impl Category {
fn handle(self, music_hoard: &mut MH) {
match self {
Category::Artist(artist_command) => artist_command.handle(music_hoard),
}
}
}
#[derive(StructOpt, Debug)]
enum ArtistCommand {
#[structopt(about = "Add a new artist to the collection")]
New(ArtistValue),
#[structopt(about = "Delete an artist from the collection")]
Delete(ArtistValue),
#[structopt(name = "musicbrainz", about = "Edit the MusicBrainz URL of an artist")]
MusicBrainz(UrlCommand<SingleUrlValue>),
#[structopt(
name = "musicbutler",
about = "Edit the MusicButler URL(s) of an artist"
)]
MusicButler(UrlCommand<MultiUrlValue>),
#[structopt(about = "Edit the Bandcamp URL(s) of an artist")]
Bandcamp(UrlCommand<MultiUrlValue>),
#[structopt(about = "Edit the Qobuz URL of an artist")]
Qobuz(UrlCommand<SingleUrlValue>),
}
#[derive(StructOpt, Debug)]
struct ArtistValue {
#[structopt(help = "The name of the artist")]
artist: String,
}
#[derive(StructOpt, Debug)]
enum UrlCommand<T: StructOpt> {
#[structopt(about = "Add the provided URL(s) without overwriting existing values")]
Add(T),
#[structopt(about = "Remove the provided URL(s)")]
Remove(T),
#[structopt(about = "Set the provided URL(s) overwriting any existing values")]
Set(T),
#[structopt(about = "Clear all URL(s)")]
Clear(ArtistValue),
}
#[derive(StructOpt, Debug)]
struct SingleUrlValue {
#[structopt(help = "The name of the artist")]
artist: String,
#[structopt(help = "The URL")]
url: String,
}
#[derive(StructOpt, Debug)]
struct MultiUrlValue {
#[structopt(help = "The name of the artist")]
artist: String,
#[structopt(help = "The list of URLs")]
urls: Vec<String>,
}
macro_rules! url_command_dispatch {
($cmd:ident, $mh:ident, $field:ident, $url:ident) => {
paste! {
match $cmd {
UrlCommand::Add(url_value) => {
$mh.[<add_ $field _ $url>](ArtistId::new(url_value.artist), url_value.$url)
.expect("failed to add URL(s)");
}
UrlCommand::Remove(url_value) => {
$mh.[<remove_ $field _ $url>](ArtistId::new(url_value.artist), url_value.$url)
.expect("failed to remove URL(s)");
}
UrlCommand::Set(url_value) => {
$mh.[<set_ $field _ $url>](ArtistId::new(url_value.artist), url_value.$url)
.expect("failed to set URL(s)");
}
UrlCommand::Clear(artist_value) => {
$mh.[<clear_ $field _ $url>](ArtistId::new(artist_value.artist))
.expect("failed to clear URL(s)");
}
}
}
};
}
macro_rules! single_url_command_dispatch {
($cmd:ident, $mh:ident, $field:ident) => {
url_command_dispatch!($cmd, $mh, $field, url)
};
}
macro_rules! multi_url_command_dispatch {
($cmd:ident, $mh:ident, $field:ident) => {
url_command_dispatch!($cmd, $mh, $field, urls)
};
}
impl ArtistCommand {
fn handle(self, music_hoard: &mut MH) {
match self {
ArtistCommand::New(artist_value) => {
music_hoard
.new_artist(ArtistId::new(artist_value.artist))
.expect("failed to add new artist");
}
ArtistCommand::Delete(artist_value) => {
music_hoard
.delete_artist(ArtistId::new(artist_value.artist))
.expect("failed to delete artist");
}
ArtistCommand::MusicBrainz(url_command) => {
single_url_command_dispatch!(url_command, music_hoard, musicbrainz)
}
ArtistCommand::MusicButler(url_command) => {
multi_url_command_dispatch!(url_command, music_hoard, musicbutler)
}
ArtistCommand::Bandcamp(url_command) => {
multi_url_command_dispatch!(url_command, music_hoard, bandcamp)
}
ArtistCommand::Qobuz(url_command) => {
single_url_command_dispatch!(url_command, music_hoard, qobuz)
}
}
}
}
fn main() {
let opt = Opt::from_args();
let lib: Option<NoLibrary> = None;
let db = Some(JsonDatabase::new(JsonDatabaseFileBackend::new(
&opt.database_file_path,
)));
let mut music_hoard = MusicHoard::new(lib, db);
music_hoard
.load_from_database()
.expect("failed to load database");
opt.category.handle(&mut music_hoard);
music_hoard
.save_to_database()
.expect("failed to save database");
}

1053
src/lib.rs

File diff suppressed because it is too large Load Diff