From fc95572d4663b51fe1747f55eed030fe9630e651 Mon Sep 17 00:00:00 2001 From: Peter Simonsson Date: Tue, 7 May 2024 02:54:56 +0200 Subject: [PATCH] Error handling (#96) * Cleaner error handling with Result type alias * Make clippy happy --- src/cli.rs | 36 ++++++++++++++++-------------------- src/configs.rs | 25 ++++++++++++++----------- src/dirty_paths.rs | 12 ++++++------ src/error.rs | 2 ++ src/lib.rs | 10 +++++----- src/main.rs | 8 ++++---- src/picker.rs | 6 +++--- src/repos.rs | 10 +++++----- 8 files changed, 55 insertions(+), 54 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 730a23c..88bb3c8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,10 +8,10 @@ use crate::{ repos::{find_repos, RepoContainer}, session_exists, set_up_tmux_env, switch_to_session, tmux::Tmux, - TmsError, + Result, TmsError, }; use clap::{Args, Parser, Subcommand}; -use error_stack::{Result, ResultExt}; +use error_stack::ResultExt; use git2::{build::RepoBuilder, FetchOptions, RemoteCallbacks, Repository}; #[derive(Debug, Parser)] @@ -115,7 +115,7 @@ pub struct CloneRepoCommand { } impl Cli { - pub fn handle_sub_commands(&self, tmux: &Tmux) -> Result { + pub fn handle_sub_commands(&self, tmux: &Tmux) -> Result { // Get the configuration from the config file let config = Config::new().change_context(TmsError::ConfigError)?; @@ -174,7 +174,7 @@ impl Cli { } } -fn start_command(config: Config, tmux: &Tmux) -> Result<(), TmsError> { +fn start_command(config: Config, tmux: &Tmux) -> Result<()> { if let Some(sessions) = &config.sessions { for session in sessions { let session_path = session @@ -212,7 +212,7 @@ fn start_command(config: Config, tmux: &Tmux) -> Result<(), TmsError> { Ok(()) } -fn switch_command(config: Config, tmux: &Tmux) -> Result<(), TmsError> { +fn switch_command(config: Config, tmux: &Tmux) -> Result<()> { let sessions = tmux .list_sessions("'#{?session_attached,,#{session_name}#,#{session_last_attached}}'") .replace('\'', "") @@ -231,7 +231,7 @@ fn switch_command(config: Config, tmux: &Tmux) -> Result<(), TmsError> { let mut sessions: Vec = sessions.into_iter().map(|s| s.0.to_string()).collect(); if let Some(true) = config.switch_filter_unknown { let repos = find_repos( - config.search_dirs()?, + config.search_dirs().change_context(TmsError::ConfigError)?, config.excluded_dirs, config.display_full_path, config.search_submodules, @@ -257,7 +257,7 @@ fn switch_command(config: Config, tmux: &Tmux) -> Result<(), TmsError> { Ok(()) } -fn windows_command(config: Config, tmux: &Tmux) -> Result<(), TmsError> { +fn windows_command(config: Config, tmux: &Tmux) -> Result<()> { let windows = tmux.list_windows("'#{?window_attached,,#{window_name}}'", None); let windows: Vec = windows @@ -280,7 +280,7 @@ fn windows_command(config: Config, tmux: &Tmux) -> Result<(), TmsError> { Ok(()) } -fn config_command(args: &ConfigCommand, mut config: Config) -> Result<(), TmsError> { +fn config_command(args: &ConfigCommand, mut config: Config) -> Result<()> { let max_depths = args.max_depths.clone().unwrap_or_default(); config.search_dirs = match &args.search_paths { Some(paths) => Some( @@ -299,14 +299,14 @@ fn config_command(args: &ConfigCommand, mut config: Config) -> Result<(), TmsErr .map(|val| (val.to_string(), depth)) .change_context(TmsError::IoError) }) - .collect::, TmsError>>()? + .collect::>>()? .iter() .map(|(path, depth)| { canonicalize(path) .map(|val| SearchDirectory::new(val, *depth)) .change_context(TmsError::IoError) }) - .collect::, TmsError>>()?, + .collect::>>()?, ), None => config.search_dirs, }; @@ -393,7 +393,7 @@ fn config_command(args: &ConfigCommand, mut config: Config) -> Result<(), TmsErr Ok(()) } -fn kill_subcommand(config: Config, tmux: &Tmux) -> Result<(), TmsError> { +fn kill_subcommand(config: Config, tmux: &Tmux) -> Result<()> { let mut current_session = tmux.display_message("'#S'"); current_session.retain(|x| x != '\'' && x != '\n'); @@ -430,7 +430,7 @@ fn kill_subcommand(config: Config, tmux: &Tmux) -> Result<(), TmsError> { Ok(()) } -fn sessions_subcommand(tmux: &Tmux) -> Result<(), TmsError> { +fn sessions_subcommand(tmux: &Tmux) -> Result<()> { let mut current_session = tmux.display_message("'#S'"); current_session.retain(|x| x != '\'' && x != '\n'); let current_session_star = format!("{current_session}*"); @@ -458,7 +458,7 @@ fn sessions_subcommand(tmux: &Tmux) -> Result<(), TmsError> { Ok(()) } -fn rename_subcommand(args: &RenameCommand, tmux: &Tmux) -> Result<(), TmsError> { +fn rename_subcommand(args: &RenameCommand, tmux: &Tmux) -> Result<()> { let new_session_name = &args.name; let current_session = tmux.display_message("'#S'"); @@ -513,7 +513,7 @@ fn rename_subcommand(args: &RenameCommand, tmux: &Tmux) -> Result<(), TmsError> Ok(()) } -fn refresh_command(args: &RefreshCommand, tmux: &Tmux) -> Result<(), TmsError> { +fn refresh_command(args: &RefreshCommand, tmux: &Tmux) -> Result<()> { let session_name = args .name .clone() @@ -569,11 +569,7 @@ fn refresh_command(args: &RefreshCommand, tmux: &Tmux) -> Result<(), TmsError> { Ok(()) } -fn clone_repo_command( - args: &CloneRepoCommand, - config: Config, - tmux: &Tmux, -) -> Result<(), TmsError> { +fn clone_repo_command(args: &CloneRepoCommand, config: Config, tmux: &Tmux) -> Result<()> { let search_dirs = config .search_dirs .ok_or(TmsError::ConfigError) @@ -615,7 +611,7 @@ fn clone_repo_command( Ok(()) } -fn git_clone(repo: &str, target: &Path) -> Result { +fn git_clone(repo: &str, target: &Path) -> Result { let mut callbacks = RemoteCallbacks::new(); callbacks.credentials(git_credentials_callback); let mut fo = FetchOptions::new(); diff --git a/src/configs.rs b/src/configs.rs index f15eba0..d9d0869 100644 --- a/src/configs.rs +++ b/src/configs.rs @@ -1,11 +1,13 @@ use clap::ValueEnum; -use error_stack::{Result, ResultExt}; +use error_stack::ResultExt; use serde_derive::{Deserialize, Serialize}; use std::{env, fmt::Display, fs::canonicalize, io::Write, path::PathBuf}; use ratatui::style::{Color, Style}; -use crate::{keymap::Keymap, Suggestion, TmsError}; +use crate::{keymap::Keymap, Suggestion}; + +type Result = error_stack::Result; #[derive(Debug)] pub enum ConfigError { @@ -13,6 +15,7 @@ pub enum ConfigError { LoadError, TomlError, FileWriteError, + IoError, } impl std::error::Error for ConfigError {} @@ -24,6 +27,7 @@ impl Display for ConfigError { Self::TomlError => write!(f, "Could not serialize config to TOML"), Self::FileWriteError => write!(f, "Could not write to config file"), Self::LoadError => write!(f, "Could not load configuration"), + Self::IoError => write!(f, "IO error"), } } } @@ -45,7 +49,7 @@ pub struct Config { } impl Config { - pub(crate) fn new() -> Result { + pub(crate) fn new() -> Result { let config_builder = match env::var("TMS_CONFIG_FILE") { Ok(path) => { config::Config::builder().add_source(config::File::with_name(&path).required(false)) @@ -88,7 +92,7 @@ impl Config { .attach_printable("Could not deserialize configuration") } - pub(crate) fn save(&self) -> Result<(), ConfigError> { + pub(crate) fn save(&self) -> Result<()> { let toml_pretty = toml::to_string_pretty(self) .change_context(ConfigError::TomlError)? .into_bytes(); @@ -127,18 +131,18 @@ impl Config { Ok(()) } - pub fn search_dirs(&self) -> Result, TmsError> { + pub fn search_dirs(&self) -> Result> { let mut search_dirs = if let Some(search_dirs) = self.search_dirs.as_ref() { search_dirs .iter() .map(|search_dir| { let expanded_path = shellexpand::full(&search_dir.path.to_string_lossy()) - .change_context(TmsError::IoError) + .change_context(ConfigError::IoError) .unwrap() .to_string(); let path = canonicalize(expanded_path) - .change_context(TmsError::IoError) + .change_context(ConfigError::IoError) .unwrap(); SearchDirectory::new(path, search_dir.depth) @@ -155,11 +159,11 @@ impl Config { SearchDirectory::new( canonicalize( shellexpand::full(&path) - .change_context(TmsError::IoError) + .change_context(ConfigError::IoError) .unwrap() .to_string(), ) - .change_context(TmsError::IoError) + .change_context(ConfigError::IoError) .unwrap(), 10, ) @@ -171,8 +175,7 @@ impl Config { return Err(ConfigError::NoDefaultSearchPath) .attach_printable( "You must configure at least one default search path with the `config` subcommand. E.g `tms config` ", - ) - .change_context(TmsError::ConfigError); + ); } Ok(search_dirs) diff --git a/src/dirty_paths.rs b/src/dirty_paths.rs index 74498ef..c668763 100644 --- a/src/dirty_paths.rs +++ b/src/dirty_paths.rs @@ -1,13 +1,13 @@ -use error_stack::{Result, ResultExt}; +use error_stack::ResultExt; -use crate::error::TmsError; +use crate::{Result, TmsError}; pub trait DirtyUtf8Path { - fn to_string(&self) -> Result; + fn to_string(&self) -> Result; } impl DirtyUtf8Path for std::path::PathBuf { - fn to_string(&self) -> Result { + fn to_string(&self) -> Result { Ok(self .to_str() .ok_or(TmsError::NonUtf8Path) @@ -16,7 +16,7 @@ impl DirtyUtf8Path for std::path::PathBuf { } } impl DirtyUtf8Path for std::path::Path { - fn to_string(&self) -> Result { + fn to_string(&self) -> Result { Ok(self .to_str() .ok_or(TmsError::NonUtf8Path) @@ -25,7 +25,7 @@ impl DirtyUtf8Path for std::path::Path { } } impl DirtyUtf8Path for std::ffi::OsStr { - fn to_string(&self) -> Result { + fn to_string(&self) -> Result { Ok(self .to_str() .ok_or(TmsError::NonUtf8Path) diff --git a/src/error.rs b/src/error.rs index 8c9c8c9..d4f71ce 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,7 @@ use std::{error::Error, fmt::Display}; +pub type Result = error_stack::Result; + #[derive(Debug)] pub enum TmsError { GitError, diff --git a/src/lib.rs b/src/lib.rs index 7f08d2f..87adc2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,14 +7,14 @@ pub mod picker; pub mod repos; pub mod tmux; -use error_stack::{Result, ResultExt}; +use error_stack::ResultExt; use git2::Repository; use std::{fmt::Display, process}; use crate::{ configs::PickerColorConfig, dirty_paths::DirtyUtf8Path, - error::TmsError, + error::{Result, TmsError}, keymap::Keymap, picker::{Picker, Preview}, tmux::Tmux, @@ -43,7 +43,7 @@ pub fn session_exists(repo_short_name: &str, tmux: &Tmux) -> bool { }) } -pub fn set_up_tmux_env(repo: &Repository, repo_name: &str, tmux: &Tmux) -> Result<(), TmsError> { +pub fn set_up_tmux_env(repo: &Repository, repo_name: &str, tmux: &Tmux) -> Result<()> { if repo.is_bare() { if repo .worktrees() @@ -104,10 +104,10 @@ pub fn get_single_selection( colors: Option, keymap: Option, tmux: Tmux, -) -> Result, TmsError> { +) -> Result> { let mut picker = Picker::new(list, preview, keymap, tmux).set_colors(colors); - Ok(picker.run()?) + picker.run() } #[derive(Debug)] pub struct Suggestion(&'static str); diff --git a/src/main.rs b/src/main.rs index 04cba40..31789d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ use clap::Parser; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ResultExt}; use tms::{ cli::{Cli, SubCommandGiven}, dirty_paths::DirtyUtf8Path, - error::TmsError, + error::{Result, TmsError}, get_single_selection, picker::Preview, repos::find_repos, @@ -14,7 +14,7 @@ use tms::{ Suggestion, }; -fn main() -> Result<(), TmsError> { +fn main() -> Result<()> { // Install debug hooks for formatting of error handling Report::install_debug_hook::(|value, context| { context.push_body(format!("{value}")); @@ -34,7 +34,7 @@ fn main() -> Result<(), TmsError> { // Find repositories and present them with the fuzzy finder let repos = find_repos( - config.search_dirs()?, + config.search_dirs().change_context(TmsError::ConfigError)?, config.excluded_dirs, config.display_full_path, config.search_submodules, diff --git a/src/picker.rs b/src/picker.rs index 7f28bfe..33fff93 100644 --- a/src/picker.rs +++ b/src/picker.rs @@ -30,7 +30,7 @@ use crate::{ configs::PickerColorConfig, keymap::{default_keymap, Keymap, PickerAction}, tmux::Tmux, - TmsError, + Result, TmsError, }; pub enum Preview { @@ -87,7 +87,7 @@ impl Picker { self } - pub fn run(&mut self) -> Result, TmsError> { + pub fn run(&mut self) -> Result> { enable_raw_mode().map_err(|e| TmsError::TuiError(e.to_string()))?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen).map_err(|e| TmsError::TuiError(e.to_string()))?; @@ -111,7 +111,7 @@ impl Picker { fn main_loop( &mut self, terminal: &mut Terminal>, - ) -> Result, TmsError> { + ) -> Result> { loop { terminal .draw(|f| self.render(f)) diff --git a/src/repos.rs b/src/repos.rs index ff4d2c1..0cc7526 100644 --- a/src/repos.rs +++ b/src/repos.rs @@ -1,5 +1,5 @@ use aho_corasick::{AhoCorasickBuilder, MatchKind}; -use error_stack::{Result, ResultExt}; +use error_stack::ResultExt; use git2::{Repository, Submodule}; use std::{ collections::{HashMap, VecDeque}, @@ -7,7 +7,7 @@ use std::{ path::Path, }; -use crate::{configs::SearchDirectory, dirty_paths::DirtyUtf8Path, TmsError}; +use crate::{configs::SearchDirectory, dirty_paths::DirtyUtf8Path, Result, TmsError}; pub trait RepoContainer { fn find_repo(&self, name: &str) -> Option<&Repository>; @@ -38,7 +38,7 @@ pub fn find_repos( display_full_path: Option, search_submodules: Option, recursive_submodules: Option, -) -> Result { +) -> Result { let mut repos = HashMap::new(); let mut to_search = VecDeque::new(); @@ -95,7 +95,7 @@ pub fn find_repos( Ok(repos) } -fn get_repo_name(path: &Path, repos: &impl RepoContainer) -> Result { +fn get_repo_name(path: &Path, repos: &impl RepoContainer) -> Result { let mut repo_name = path .file_name() .expect("The file name doesn't end in `..`") @@ -124,7 +124,7 @@ fn find_submodules( repos: &mut impl RepoContainer, display_full_path: Option, recursive: Option, -) -> Result<(), TmsError> { +) -> Result<()> { for submodule in submodules.iter() { let repo = match submodule.open() { Ok(repo) => repo,