From 1ee2fe47469c7d8ca4b9816d2d5f24dca4b7d2ee Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 16 Jul 2020 14:31:00 +0200 Subject: [PATCH] Use anyhow for formatting and handling errors This works better than doing it manually, and it significantly reduces the amount of boilerplate needed. --- tools/yabridgectl/Cargo.lock | 7 ++ tools/yabridgectl/Cargo.toml | 1 + tools/yabridgectl/src/actions.rs | 118 ++++++++++++------------------- tools/yabridgectl/src/config.rs | 51 ++++++------- tools/yabridgectl/src/main.rs | 11 +-- 5 files changed, 76 insertions(+), 112 deletions(-) diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index bbc71b40..0faad40f 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -9,6 +9,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" + [[package]] name = "arrayref" version = "0.3.6" @@ -665,6 +671,7 @@ name = "yabridgectl" version = "1.2.1" dependencies = [ "aho-corasick", + "anyhow", "clap", "colored", "lazy_static", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index af272b6f..b67a0cb7 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/robbert-vdh/yabridge" license = "GPL-3.0-or-later" [dependencies] +anyhow = "1.0.31" aho-corasick = "0.7.13" colored = "2.0.0" clap = { version = "3.0.0-beta.1", features = ["wrap_help"] } diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 4aeeb965..0972252c 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -16,36 +16,30 @@ //! Handlers for the subcommands, just to keep `main.rs` clean. +use anyhow::{Context, Result}; use clap::ArgMatches; use colored::Colorize; use std::fs; use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; -use std::process::exit; use crate::config::{Config, InstallationMethod}; use crate::files; use crate::files::FoundFile; /// 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.write().unwrap_or_else(|err| { - eprintln!("Error while writing config file: {}", err); - exit(1); - }); + config.write() } /// Remove a direcotry to the plugin locations. The path is assumed to be part of /// `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` // XXS: Would it be a good idea to warn about leftover .so files? config.plugin_dirs.remove(path); - config.write().unwrap_or_else(|err| { - eprintln!("Error while writing config file: {}", err); - exit(1); - }); + config.write()?; // Ask the user to remove any leftover files to prevent possible future problems and out of date // copies @@ -66,10 +60,8 @@ pub fn remove_directory(config: &mut Config, path: &Path) { ) { Ok(Some(answer)) if answer == "YES" => { for file in &orphan_files { - fs::remove_file(file.path()).unwrap_or_else(|err| { - eprintln!("Could not remove '{}': {}", file.path().display(), err); - exit(1); - }); + fs::remove_file(file.path()) + .with_context(|| format!("Could not remove '{}'", file.path().display()))?; } println!("\nRemoved {} files.", orphan_files.len()); @@ -77,24 +69,24 @@ pub fn remove_directory(config: &mut Config, path: &Path) { _ => {} } } + + Ok(()) } /// List the plugin locations. -pub fn list_directories(config: &Config) { +pub fn list_directories(config: &Config) -> Result<()> { for directory in &config.plugin_dirs { println!("{}", directory.display()); } + + Ok(()) } /// Print the current configuration and the installation status for all found plugins. -pub fn show_status(config: &Config) { - let results = match config.index_directories() { - Ok(results) => results, - Err(err) => { - eprintln!("Error while searching for plugins: {}", err); - exit(1); - } - }; +pub fn show_status(config: &Config) -> Result<()> { + let results = config + .index_directories() + .context("Failure while searching for plugins")?; println!( "yabridge path: {}", @@ -126,10 +118,12 @@ pub fn show_status(config: &Config) { println!(" {} :: {}", plugin.display(), status_str); } } + + Ok(()) } /// 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") { Some("copy") => config.method = InstallationMethod::Copy, Some("symlink") => config.method = InstallationMethod::Symlink, @@ -148,34 +142,18 @@ pub fn set_settings(config: &mut Config, options: &ArgMatches) { Err(err) => err.exit(), } - config.write().unwrap_or_else(|err| { - eprintln!("Error while writing config file: {}", err); - exit(1); - }); + config.write() } /// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan /// `.so` files if the prune option is set. -pub fn do_sync(config: &Config, prune: bool, verbose: bool) { - let libyabridge_path = match config.libyabridge() { - Ok(path) => { - println!("Using '{}'\n", path.display()); - path - } - Err(err) => { - // The error messages here are already formatted - eprintln!("{}", err); - exit(1); - } - }; +pub fn do_sync(config: &Config, prune: bool, verbose: bool) -> Result<()> { + let libyabridge_path = config.libyabridge()?; + println!("Using '{}'\n", libyabridge_path.display()); - let results = match config.index_directories() { - Ok(results) => results, - Err(err) => { - eprintln!("Error while searching for plugins: {}", err); - exit(1); - } - }; + let results = config + .index_directories() + .context("Failure while searching for plugins")?; // Keep track of some global statistics let mut num_installed = 0; @@ -194,34 +172,28 @@ pub fn do_sync(config: &Config, prune: bool, verbose: bool) { // mixing symlinks and regular files let target_path = plugin.with_extension("so"); if target_path.exists() { - fs::remove_file(&target_path).unwrap_or_else(|err| { - eprintln!("Could not remove '{}': {}", target_path.display(), err); - exit(1); - }); + fs::remove_file(&target_path) + .with_context(|| format!("Could not remove '{}'", target_path.display()))?; } match config.method { InstallationMethod::Copy => { - fs::copy(&libyabridge_path, &target_path).unwrap_or_else(|err| { - eprintln!( - "Error copying '{}' to '{}': {}", + fs::copy(&libyabridge_path, &target_path).with_context(|| { + format!( + "Error copying '{}' to '{}'", libyabridge_path.display(), - target_path.display(), - err - ); - exit(1); - }); + target_path.display() + ) + })?; } InstallationMethod::Symlink => { - symlink(&libyabridge_path, &target_path).unwrap_or_else(|err| { - eprintln!( - "Error symlinking '{}' to '{}': {}", + symlink(&libyabridge_path, &target_path).with_context(|| { + format!( + "Error symlinking '{}' to '{}'", libyabridge_path.display(), - target_path.display(), - err - ); - exit(1); - }); + target_path.display() + ) + })?; } } @@ -261,10 +233,8 @@ pub fn do_sync(config: &Config, prune: bool, verbose: bool) { println!("- {}", path.display()); if prune { - fs::remove_file(path).unwrap_or_else(|err| { - eprintln!("Error while trying to remove '{}': {}", path.display(), err); - exit(1); - }); + fs::remove_file(path) + .with_context(|| format!("Could not remove '{}'", path.display()))?; } } @@ -276,5 +246,7 @@ pub fn do_sync(config: &Config, prune: bool, verbose: bool) { num_installed, config.method.plural(), num_skipped_files - ) + ); + + Ok(()) } diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 1b378ebb..daab693d 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -16,6 +16,7 @@ //! Utilities for managing yabrigectl's configuration. +use anyhow::{anyhow, Context, Result}; use rayon::prelude::*; use serde_derive::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; @@ -90,19 +91,15 @@ impl Display for InstallationMethod { impl Config { /// 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. - pub fn read() -> Result { + pub fn read() -> Result { match yabridgectl_directories()?.find_config_file(CONFIG_FILE_NAME) { Some(path) => { - let toml_str = fs::read_to_string(&path).map_err(|err| { - format!( - "Could not read config file at '{}': {}", - path.display(), - err - ) + let toml_str = fs::read_to_string(&path).with_context(|| { + format!("Could not read config file at '{}'", path.display()) })?; - Ok(toml::from_str(&toml_str) - .map_err(|err| format!("Could not parse TOML: {}", err))?) + toml::from_str(&toml_str) + .with_context(|| format!("Failed to parse '{}'", path.display())) } None => { let defaults = Config { @@ -121,34 +118,28 @@ impl Config { } /// Write the config to disk, creating the file if it does not yet exist. - pub fn write(&self) -> Result<(), String> { - let toml_str = toml::to_string_pretty(&self) - .map_err(|err| format!("Could not format TOML: {}", err))?; - let config_file = yabridgectl_directories()? + pub fn write(&self) -> Result<()> { + let toml_str = toml::to_string_pretty(&self).context("Could not format TOML")?; + let config_path = yabridgectl_directories()? .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| { - format!( - "Could not write config file to '{}': {}", - config_file.display(), - err - ) - }) + fs::write(&config_path, toml_str) + .with_context(|| format!("Failed to write config file to '{}'", config_path.display())) } /// 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 /// `$XDG_DATA_HOME/yabridge`. - pub fn libyabridge(&self) -> Result { + pub fn libyabridge(&self) -> Result { match &self.yabridge_home { Some(directory) => { let candidate = directory.join(LIBYABRIDGE_NAME); if candidate.exists() { Ok(candidate) } else { - Err(format!( - "Could not find '{}' in '{}'.", + Err(anyhow!( + "Could not find '{}' in '{}'", LIBYABRIDGE_NAME, directory.display() )) @@ -165,7 +156,7 @@ impl Config { } } - Err(format!( + Err(anyhow!( "Could not find '{}' in either '{}' or '{}'. You can tell yabridgectl where \ to search for it using 'yabridgectl set --path='.", LIBYABRIDGE_NAME, @@ -189,14 +180,12 @@ impl Config { /// 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 /// for `libyabridge.so` when no explicit search path has been set. -fn yabridge_directories() -> Result { - BaseDirectories::with_prefix(YABRIDGE_PREFIX) - .map_err(|err| format!("Error while parsing base directories: {}", err)) +fn yabridge_directories() -> Result { + BaseDirectories::with_prefix(YABRIDGE_PREFIX).context("Error while parsing base directories") } /// Fetch the XDG base directories used for yabridgectl, converting any error messages if this /// somehow fails into a printable string to reduce boiler plate. -fn yabridgectl_directories() -> Result { - BaseDirectories::with_prefix(YABRIDGECTL_PREFIX) - .map_err(|err| format!("Error while parsing base directories: {}", err)) +fn yabridgectl_directories() -> Result { + BaseDirectories::with_prefix(YABRIDGECTL_PREFIX).context("Error while parsing base directories") } diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 4a23b60b..440bf0d2 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use anyhow::Result; use clap::{app_from_crate, App, AppSettings, Arg}; use colored::Colorize; 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: Reward parts of the readme -fn main() { - let mut config = match Config::read() { - Ok(config) => config, - Err(err) => { - eprintln!("Error while reading config:\n\n{}", err); - std::process::exit(1); - } - }; +fn main() -> Result<()> { + let mut config = Config::read()?; // Used for validation in `yabridgectl rm ` let plugin_directories: Vec<&str> = config