Add method to manually add artist metadata #85
@ -30,6 +30,7 @@ jobs:
|
|||||||
--ignore-not-existing
|
--ignore-not-existing
|
||||||
--ignore "tests/*"
|
--ignore "tests/*"
|
||||||
--ignore "src/main.rs"
|
--ignore "src/main.rs"
|
||||||
|
--ignore "src/bin/mh-edit.rs"
|
||||||
--excl-start "GRCOV_EXCL_START|mod tests \{"
|
--excl-start "GRCOV_EXCL_START|mod tests \{"
|
||||||
--excl-stop "GRCOV_EXCL_STOP"
|
--excl-stop "GRCOV_EXCL_STOP"
|
||||||
--output-path ./target/debug/coverage/
|
--output-path ./target/debug/coverage/
|
||||||
|
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -369,6 +369,7 @@ dependencies = [
|
|||||||
"mockall",
|
"mockall",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"openssh",
|
"openssh",
|
||||||
|
"paste",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -456,6 +457,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
10
Cargo.toml
10
Cargo.toml
@ -8,6 +8,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = { version = "0.26.1", optional = true}
|
crossterm = { version = "0.26.1", optional = true}
|
||||||
openssh = { version = "0.9.9", features = ["native-mux"], default-features = false, 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}
|
ratatui = { version = "0.20.1", optional = true}
|
||||||
serde = { version = "1.0.159", features = ["derive"] }
|
serde = { version = "1.0.159", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", optional = true}
|
serde_json = { version = "1.0.95", optional = true}
|
||||||
@ -23,14 +24,19 @@ tempfile = "3.5.0"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["database-json", "library-beets"]
|
default = ["database-json", "library-beets"]
|
||||||
|
bin = ["structopt"]
|
||||||
database-json = ["serde_json"]
|
database-json = ["serde_json"]
|
||||||
library-beets = []
|
library-beets = []
|
||||||
ssh-library = ["openssh", "tokio"]
|
ssh-library = ["openssh", "tokio"]
|
||||||
tui = ["structopt", "crossterm", "ratatui"]
|
tui = ["crossterm", "ratatui"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "musichoard"
|
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]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
@ -32,6 +32,7 @@ grcov codecov/debug/profraw \
|
|||||||
--ignore-not-existing \
|
--ignore-not-existing \
|
||||||
--ignore "tests/*" \
|
--ignore "tests/*" \
|
||||||
--ignore "src/main.rs" \
|
--ignore "src/main.rs" \
|
||||||
|
--ignore "src/bin/mh-edit.rs" \
|
||||||
--excl-start "GRCOV_EXCL_START|mod tests \{" \
|
--excl-start "GRCOV_EXCL_START|mod tests \{" \
|
||||||
--excl-stop "GRCOV_EXCL_STOP" \
|
--excl-stop "GRCOV_EXCL_STOP" \
|
||||||
--output-path ./codecov/debug/coverage/
|
--output-path ./codecov/debug/coverage/
|
||||||
|
179
src/bin/mh-edit.rs
Normal file
179
src/bin/mh-edit.rs
Normal 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
1053
src/lib.rs
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user