mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Use anyhow for formatting and handling errors
This works better than doing it manually, and it significantly reduces the amount of boilerplate needed.
This commit is contained in:
Generated
+7
@@ -9,6 +9,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -665,6 +671,7 @@ name = "yabridgectl"
|
|||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ repository = "https://github.com/robbert-vdh/yabridge"
|
|||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.31"
|
||||||
aho-corasick = "0.7.13"
|
aho-corasick = "0.7.13"
|
||||||
colored = "2.0.0"
|
colored = "2.0.0"
|
||||||
clap = { version = "3.0.0-beta.1", features = ["wrap_help"] }
|
clap = { version = "3.0.0-beta.1", features = ["wrap_help"] }
|
||||||
|
|||||||
@@ -16,36 +16,30 @@
|
|||||||
|
|
||||||
//! Handlers for the subcommands, just to keep `main.rs` clean.
|
//! Handlers for the subcommands, just to keep `main.rs` clean.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix::fs::symlink;
|
use std::os::unix::fs::symlink;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use crate::config::{Config, InstallationMethod};
|
use crate::config::{Config, InstallationMethod};
|
||||||
use crate::files;
|
use crate::files;
|
||||||
use crate::files::FoundFile;
|
use crate::files::FoundFile;
|
||||||
|
|
||||||
/// Add a direcotry to the plugin locations. Duplicates get ignord because we're using ordered sets.
|
/// Add a direcotry to the plugin locations. Duplicates get ignord because we're using ordered sets.
|
||||||
pub fn add_directory(config: &mut Config, path: PathBuf) {
|
pub fn add_directory(config: &mut Config, path: PathBuf) -> Result<()> {
|
||||||
config.plugin_dirs.insert(path);
|
config.plugin_dirs.insert(path);
|
||||||
config.write().unwrap_or_else(|err| {
|
config.write()
|
||||||
eprintln!("Error while writing config file: {}", err);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a direcotry to the plugin locations. The path is assumed to be part of
|
/// Remove a direcotry to the plugin locations. The path is assumed to be part of
|
||||||
/// `config.plugin_dirs`, otherwise this si silently ignored.
|
/// `config.plugin_dirs`, otherwise this si silently ignored.
|
||||||
pub fn remove_directory(config: &mut Config, path: &Path) {
|
pub fn remove_directory(config: &mut Config, path: &Path) -> Result<()> {
|
||||||
// We've already verified that this path is in `config.plugin_dirs`
|
// We've already verified that this path is in `config.plugin_dirs`
|
||||||
// XXS: Would it be a good idea to warn about leftover .so files?
|
// XXS: Would it be a good idea to warn about leftover .so files?
|
||||||
config.plugin_dirs.remove(path);
|
config.plugin_dirs.remove(path);
|
||||||
config.write().unwrap_or_else(|err| {
|
config.write()?;
|
||||||
eprintln!("Error while writing config file: {}", err);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ask the user to remove any leftover files to prevent possible future problems and out of date
|
// Ask the user to remove any leftover files to prevent possible future problems and out of date
|
||||||
// copies
|
// copies
|
||||||
@@ -66,10 +60,8 @@ pub fn remove_directory(config: &mut Config, path: &Path) {
|
|||||||
) {
|
) {
|
||||||
Ok(Some(answer)) if answer == "YES" => {
|
Ok(Some(answer)) if answer == "YES" => {
|
||||||
for file in &orphan_files {
|
for file in &orphan_files {
|
||||||
fs::remove_file(file.path()).unwrap_or_else(|err| {
|
fs::remove_file(file.path())
|
||||||
eprintln!("Could not remove '{}': {}", file.path().display(), err);
|
.with_context(|| format!("Could not remove '{}'", file.path().display()))?;
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nRemoved {} files.", orphan_files.len());
|
println!("\nRemoved {} files.", orphan_files.len());
|
||||||
@@ -77,24 +69,24 @@ pub fn remove_directory(config: &mut Config, path: &Path) {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List the plugin locations.
|
/// List the plugin locations.
|
||||||
pub fn list_directories(config: &Config) {
|
pub fn list_directories(config: &Config) -> Result<()> {
|
||||||
for directory in &config.plugin_dirs {
|
for directory in &config.plugin_dirs {
|
||||||
println!("{}", directory.display());
|
println!("{}", directory.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print the current configuration and the installation status for all found plugins.
|
/// Print the current configuration and the installation status for all found plugins.
|
||||||
pub fn show_status(config: &Config) {
|
pub fn show_status(config: &Config) -> Result<()> {
|
||||||
let results = match config.index_directories() {
|
let results = config
|
||||||
Ok(results) => results,
|
.index_directories()
|
||||||
Err(err) => {
|
.context("Failure while searching for plugins")?;
|
||||||
eprintln!("Error while searching for plugins: {}", err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"yabridge path: {}",
|
"yabridge path: {}",
|
||||||
@@ -126,10 +118,12 @@ pub fn show_status(config: &Config) {
|
|||||||
println!(" {} :: {}", plugin.display(), status_str);
|
println!(" {} :: {}", plugin.display(), status_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change configuration settings. The actual options are defined in the clap [app](clap::App).
|
/// Change configuration settings. The actual options are defined in the clap [app](clap::App).
|
||||||
pub fn set_settings(config: &mut Config, options: &ArgMatches) {
|
pub fn set_settings(config: &mut Config, options: &ArgMatches) -> Result<()> {
|
||||||
match options.value_of("method") {
|
match options.value_of("method") {
|
||||||
Some("copy") => config.method = InstallationMethod::Copy,
|
Some("copy") => config.method = InstallationMethod::Copy,
|
||||||
Some("symlink") => config.method = InstallationMethod::Symlink,
|
Some("symlink") => config.method = InstallationMethod::Symlink,
|
||||||
@@ -148,34 +142,18 @@ pub fn set_settings(config: &mut Config, options: &ArgMatches) {
|
|||||||
Err(err) => err.exit(),
|
Err(err) => err.exit(),
|
||||||
}
|
}
|
||||||
|
|
||||||
config.write().unwrap_or_else(|err| {
|
config.write()
|
||||||
eprintln!("Error while writing config file: {}", err);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan
|
/// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan
|
||||||
/// `.so` files if the prune option is set.
|
/// `.so` files if the prune option is set.
|
||||||
pub fn do_sync(config: &Config, prune: bool, verbose: bool) {
|
pub fn do_sync(config: &Config, prune: bool, verbose: bool) -> Result<()> {
|
||||||
let libyabridge_path = match config.libyabridge() {
|
let libyabridge_path = config.libyabridge()?;
|
||||||
Ok(path) => {
|
println!("Using '{}'\n", libyabridge_path.display());
|
||||||
println!("Using '{}'\n", path.display());
|
|
||||||
path
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
// The error messages here are already formatted
|
|
||||||
eprintln!("{}", err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let results = match config.index_directories() {
|
let results = config
|
||||||
Ok(results) => results,
|
.index_directories()
|
||||||
Err(err) => {
|
.context("Failure while searching for plugins")?;
|
||||||
eprintln!("Error while searching for plugins: {}", err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keep track of some global statistics
|
// Keep track of some global statistics
|
||||||
let mut num_installed = 0;
|
let mut num_installed = 0;
|
||||||
@@ -194,34 +172,28 @@ pub fn do_sync(config: &Config, prune: bool, verbose: bool) {
|
|||||||
// mixing symlinks and regular files
|
// mixing symlinks and regular files
|
||||||
let target_path = plugin.with_extension("so");
|
let target_path = plugin.with_extension("so");
|
||||||
if target_path.exists() {
|
if target_path.exists() {
|
||||||
fs::remove_file(&target_path).unwrap_or_else(|err| {
|
fs::remove_file(&target_path)
|
||||||
eprintln!("Could not remove '{}': {}", target_path.display(), err);
|
.with_context(|| format!("Could not remove '{}'", target_path.display()))?;
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match config.method {
|
match config.method {
|
||||||
InstallationMethod::Copy => {
|
InstallationMethod::Copy => {
|
||||||
fs::copy(&libyabridge_path, &target_path).unwrap_or_else(|err| {
|
fs::copy(&libyabridge_path, &target_path).with_context(|| {
|
||||||
eprintln!(
|
format!(
|
||||||
"Error copying '{}' to '{}': {}",
|
"Error copying '{}' to '{}'",
|
||||||
libyabridge_path.display(),
|
libyabridge_path.display(),
|
||||||
target_path.display(),
|
target_path.display()
|
||||||
err
|
)
|
||||||
);
|
})?;
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
InstallationMethod::Symlink => {
|
InstallationMethod::Symlink => {
|
||||||
symlink(&libyabridge_path, &target_path).unwrap_or_else(|err| {
|
symlink(&libyabridge_path, &target_path).with_context(|| {
|
||||||
eprintln!(
|
format!(
|
||||||
"Error symlinking '{}' to '{}': {}",
|
"Error symlinking '{}' to '{}'",
|
||||||
libyabridge_path.display(),
|
libyabridge_path.display(),
|
||||||
target_path.display(),
|
target_path.display()
|
||||||
err
|
)
|
||||||
);
|
})?;
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,10 +233,8 @@ pub fn do_sync(config: &Config, prune: bool, verbose: bool) {
|
|||||||
|
|
||||||
println!("- {}", path.display());
|
println!("- {}", path.display());
|
||||||
if prune {
|
if prune {
|
||||||
fs::remove_file(path).unwrap_or_else(|err| {
|
fs::remove_file(path)
|
||||||
eprintln!("Error while trying to remove '{}': {}", path.display(), err);
|
.with_context(|| format!("Could not remove '{}'", path.display()))?;
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,5 +246,7 @@ pub fn do_sync(config: &Config, prune: bool, verbose: bool) {
|
|||||||
num_installed,
|
num_installed,
|
||||||
config.method.plural(),
|
config.method.plural(),
|
||||||
num_skipped_files
|
num_skipped_files
|
||||||
)
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
//! Utilities for managing yabrigectl's configuration.
|
//! Utilities for managing yabrigectl's configuration.
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
@@ -90,19 +91,15 @@ impl Display for InstallationMethod {
|
|||||||
impl Config {
|
impl Config {
|
||||||
/// Try to read the config file, creating a new default file if necessary. This will fail if the
|
/// Try to read the config file, creating a new default file if necessary. This will fail if the
|
||||||
/// file could not be created or if it could not be parsed.
|
/// file could not be created or if it could not be parsed.
|
||||||
pub fn read() -> Result<Config, String> {
|
pub fn read() -> Result<Config> {
|
||||||
match yabridgectl_directories()?.find_config_file(CONFIG_FILE_NAME) {
|
match yabridgectl_directories()?.find_config_file(CONFIG_FILE_NAME) {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
let toml_str = fs::read_to_string(&path).map_err(|err| {
|
let toml_str = fs::read_to_string(&path).with_context(|| {
|
||||||
format!(
|
format!("Could not read config file at '{}'", path.display())
|
||||||
"Could not read config file at '{}': {}",
|
|
||||||
path.display(),
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(toml::from_str(&toml_str)
|
toml::from_str(&toml_str)
|
||||||
.map_err(|err| format!("Could not parse TOML: {}", err))?)
|
.with_context(|| format!("Failed to parse '{}'", path.display()))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let defaults = Config {
|
let defaults = Config {
|
||||||
@@ -121,34 +118,28 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write the config to disk, creating the file if it does not yet exist.
|
/// Write the config to disk, creating the file if it does not yet exist.
|
||||||
pub fn write(&self) -> Result<(), String> {
|
pub fn write(&self) -> Result<()> {
|
||||||
let toml_str = toml::to_string_pretty(&self)
|
let toml_str = toml::to_string_pretty(&self).context("Could not format TOML")?;
|
||||||
.map_err(|err| format!("Could not format TOML: {}", err))?;
|
let config_path = yabridgectl_directories()?
|
||||||
let config_file = yabridgectl_directories()?
|
|
||||||
.place_config_file(CONFIG_FILE_NAME)
|
.place_config_file(CONFIG_FILE_NAME)
|
||||||
.map_err(|err| format!("Could not write config file: {}", err))?;
|
.context("Could not create config file")?;
|
||||||
|
|
||||||
fs::write(&config_file, toml_str).map_err(|err| {
|
fs::write(&config_path, toml_str)
|
||||||
format!(
|
.with_context(|| format!("Failed to write config file to '{}'", config_path.display()))
|
||||||
"Could not write config file to '{}': {}",
|
|
||||||
config_file.display(),
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the path to `libyabridge.so`, or a descriptive error if it can't be found. If
|
/// Return the path to `libyabridge.so`, or a descriptive error if it can't be found. If
|
||||||
/// `yabridge_home` is `None`, then we'll search in both `/usr/lib` and
|
/// `yabridge_home` is `None`, then we'll search in both `/usr/lib` and
|
||||||
/// `$XDG_DATA_HOME/yabridge`.
|
/// `$XDG_DATA_HOME/yabridge`.
|
||||||
pub fn libyabridge(&self) -> Result<PathBuf, String> {
|
pub fn libyabridge(&self) -> Result<PathBuf> {
|
||||||
match &self.yabridge_home {
|
match &self.yabridge_home {
|
||||||
Some(directory) => {
|
Some(directory) => {
|
||||||
let candidate = directory.join(LIBYABRIDGE_NAME);
|
let candidate = directory.join(LIBYABRIDGE_NAME);
|
||||||
if candidate.exists() {
|
if candidate.exists() {
|
||||||
Ok(candidate)
|
Ok(candidate)
|
||||||
} else {
|
} else {
|
||||||
Err(format!(
|
Err(anyhow!(
|
||||||
"Could not find '{}' in '{}'.",
|
"Could not find '{}' in '{}'",
|
||||||
LIBYABRIDGE_NAME,
|
LIBYABRIDGE_NAME,
|
||||||
directory.display()
|
directory.display()
|
||||||
))
|
))
|
||||||
@@ -165,7 +156,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(format!(
|
Err(anyhow!(
|
||||||
"Could not find '{}' in either '{}' or '{}'. You can tell yabridgectl where \
|
"Could not find '{}' in either '{}' or '{}'. You can tell yabridgectl where \
|
||||||
to search for it using 'yabridgectl set --path=<path>'.",
|
to search for it using 'yabridgectl set --path=<path>'.",
|
||||||
LIBYABRIDGE_NAME,
|
LIBYABRIDGE_NAME,
|
||||||
@@ -189,14 +180,12 @@ impl Config {
|
|||||||
/// Fetch the XDG base directories for yabridge's own files, converting any error messages if this
|
/// Fetch the XDG base directories for yabridge's own files, converting any error messages if this
|
||||||
/// somehow fails into a printable string to reduce boiler plate. This is only used when searching
|
/// somehow fails into a printable string to reduce boiler plate. This is only used when searching
|
||||||
/// for `libyabridge.so` when no explicit search path has been set.
|
/// for `libyabridge.so` when no explicit search path has been set.
|
||||||
fn yabridge_directories() -> Result<BaseDirectories, String> {
|
fn yabridge_directories() -> Result<BaseDirectories> {
|
||||||
BaseDirectories::with_prefix(YABRIDGE_PREFIX)
|
BaseDirectories::with_prefix(YABRIDGE_PREFIX).context("Error while parsing base directories")
|
||||||
.map_err(|err| format!("Error while parsing base directories: {}", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch the XDG base directories used for yabridgectl, converting any error messages if this
|
/// Fetch the XDG base directories used for yabridgectl, converting any error messages if this
|
||||||
/// somehow fails into a printable string to reduce boiler plate.
|
/// somehow fails into a printable string to reduce boiler plate.
|
||||||
fn yabridgectl_directories() -> Result<BaseDirectories, String> {
|
fn yabridgectl_directories() -> Result<BaseDirectories> {
|
||||||
BaseDirectories::with_prefix(YABRIDGECTL_PREFIX)
|
BaseDirectories::with_prefix(YABRIDGECTL_PREFIX).context("Error while parsing base directories")
|
||||||
.map_err(|err| format!("Error while parsing base directories: {}", err))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use clap::{app_from_crate, App, AppSettings, Arg};
|
use clap::{app_from_crate, App, AppSettings, Arg};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -28,14 +29,8 @@ mod files;
|
|||||||
// TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell
|
// TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell
|
||||||
// TODO: Reward parts of the readme
|
// TODO: Reward parts of the readme
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
let mut config = match Config::read() {
|
let mut config = Config::read()?;
|
||||||
Ok(config) => config,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Error while reading config:\n\n{}", err);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used for validation in `yabridgectl rm <path>`
|
// Used for validation in `yabridgectl rm <path>`
|
||||||
let plugin_directories: Vec<&str> = config
|
let plugin_directories: Vec<&str> = config
|
||||||
|
|||||||
Reference in New Issue
Block a user