From b2c47f24cc7bd0c0bea064fb838584df525f7e94 Mon Sep 17 00:00:00 2001 From: Samyak S Sarnayak Date: Sun, 26 Jun 2022 00:47:04 +0530 Subject: [PATCH] refactor: use anyhow for error handling --- Cargo.lock | 7 ++++ Cargo.toml | 5 +-- src/lib.rs | 86 +++++++++++++++++++++++++----------------------- src/main.rs | 5 +-- src/tui.rs | 9 +++-- src/wordlists.rs | 2 +- 6 files changed, 65 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9b3ee4..217f9fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + [[package]] name = "atty" version = "0.2.14" @@ -296,6 +302,7 @@ checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" name = "toipe" version = "0.4.1" dependencies = [ + "anyhow", "bisection", "clap", "rand", diff --git a/Cargo.toml b/Cargo.toml index 256fe36..aa81357 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,8 @@ strip = "debuginfo" [lib] [dependencies] -termion = "1.5.6" -rand = "0.8.4" +anyhow = "1.0" bisection = "0.1.0" clap = { version = "3.0.5", features = ["derive", "color", "suggestions"] } +rand = "0.8.4" +termion = "1.5.6" diff --git a/src/lib.rs b/src/lib.rs index f9703cb..1240a0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ use textgen::{RawWordSelector, WordSelector}; use tui::{Text, ToipeTui}; use wordlists::{BuiltInWordlist, OS_WORDLIST_PATH}; +use anyhow::{Context, Result}; + /// Typing test terminal UI and logic. pub struct Toipe { tui: ToipeTui, @@ -38,6 +40,7 @@ pub struct Toipe { } /// Represents any error caught in Toipe. +#[derive(Debug)] pub struct ToipeError { pub msg: String, } @@ -50,32 +53,20 @@ impl ToipeError { } } -/// Converts [`std::io::Error`] to [`ToipeError`]. -/// -/// This keeps only the error message. -/// -/// TODO: there must be a better way to keep information from the -/// original error. -impl From for ToipeError { - fn from(error: std::io::Error) -> Self { - ToipeError { - msg: error.to_string(), - } - } -} - impl From for ToipeError { fn from(error: String) -> Self { ToipeError { msg: error } } } -impl std::fmt::Debug for ToipeError { +impl std::fmt::Display for ToipeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(format!("ToipeError: {}", self.msg).as_str()) } } +impl std::error::Error for ToipeError {} + impl<'a> Toipe { /// Initializes a new typing test on the standard output. /// @@ -83,27 +74,40 @@ impl<'a> Toipe { /// /// Initializes the word selector. /// Also invokes [`Toipe::restart()`]. - pub fn new(config: ToipeConfig) -> Result { - let word_selector: Result, _> = - if let Some(wordlist_path) = config.wordlist_file.clone() { - RawWordSelector::from_path(PathBuf::from(wordlist_path)) - .map(|ws| Box::new(ws) as Box) - } else if let Some(word_list) = config.wordlist.contents() { - RawWordSelector::from_string(word_list.to_string()) - .map(|ws| Box::new(ws) as Box) - } else if let BuiltInWordlist::OS = config.wordlist { - RawWordSelector::from_path(PathBuf::from(OS_WORDLIST_PATH)) - .map(|ws| Box::new(ws) as Box) - } else { - // this should never happen! - // TODO: somehow enforce this at compile time? - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Undefined word list or path.", - )) - }; - let word_selector = word_selector - .map_err(|e| ToipeError::from(e).with_context("Error reading the given word list: "))?; + pub fn new(config: ToipeConfig) -> Result { + let word_selector: Box = if let Some(wordlist_path) = + config.wordlist_file.clone() + { + Box::new( + RawWordSelector::from_path(PathBuf::from(wordlist_path.clone())).with_context( + || { + format!( + "Error reading the word list from given path '{}'", + wordlist_path + ) + }, + )?, + ) + } else if let Some(word_list) = config.wordlist.contents() { + Box::new( + RawWordSelector::from_string(word_list.to_string()).with_context(|| { + format!("Error reading the built-in word list {:?}", config.wordlist) + })?, + ) + } else if let BuiltInWordlist::OS = config.wordlist { + Box::new( + RawWordSelector::from_path(PathBuf::from(OS_WORDLIST_PATH)).with_context(|| { + format!( + "Error reading from the OS wordlist at path '{}'", + OS_WORDLIST_PATH + ) + })?, + ) + } else { + // this should never happen! + // TODO: somehow enforce this at compile time? + return Err(ToipeError::from("Undefined word list or path.".to_owned()))?; + }; let mut toipe = Toipe { tui: ToipeTui::new(), @@ -122,7 +126,7 @@ impl<'a> Toipe { /// /// Clears the screen, generates new words and displays them on the /// UI. - pub fn restart(&mut self) -> Result<(), ToipeError> { + pub fn restart(&mut self) -> Result<()> { self.tui.reset_screen()?; self.words = self.word_selector.new_words(self.config.num_words)?; @@ -139,7 +143,7 @@ impl<'a> Toipe { Ok(()) } - fn show_words(&mut self) -> Result<(), ToipeError> { + fn show_words(&mut self) -> Result<()> { self.text = self.tui.display_words(&self.words)?; Ok(()) } @@ -151,7 +155,7 @@ impl<'a> Toipe { /// If the test completes successfully, returns a boolean indicating /// whether the user wants to do another test and the /// [`ToipeResults`] for this test. - pub fn test(&mut self, stdin: StdinLock<'a>) -> Result<(bool, ToipeResults), ToipeError> { + pub fn test(&mut self, stdin: StdinLock<'a>) -> Result<(bool, ToipeResults)> { let mut input = Vec::::new(); let original_text = self .text @@ -188,7 +192,7 @@ impl<'a> Toipe { } } - let mut process_key = |key: Key| -> Result { + let mut process_key = |key: Key| -> Result { match key { Key::Ctrl('c') => { return Ok(TestStatus::Quit); @@ -301,7 +305,7 @@ impl<'a> Toipe { &mut self, results: ToipeResults, mut keys: Keys, - ) -> Result { + ) -> Result { self.tui.reset_screen()?; self.tui.display_lines::<&[Text], _>(&[ diff --git a/src/main.rs b/src/main.rs index 4492bb0..23e1443 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ +use anyhow::Result; use clap::StructOpt; + use std::io::stdin; use toipe::config::ToipeConfig; use toipe::Toipe; -use toipe::ToipeError; -fn main() -> Result<(), ToipeError> { +fn main() -> Result<()> { let config = ToipeConfig::parse(); let mut toipe = Toipe::new(config)?; diff --git a/src/tui.rs b/src/tui.rs index fcffb8e..c79165b 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -14,6 +14,7 @@ use termion::{ }; use crate::ToipeError; +use anyhow::Result; const MIN_LINE_WIDTH: usize = 50; @@ -228,7 +229,7 @@ pub struct ToipeTui { bottom_lines_len: usize, } -type MaybeError = Result; +type MaybeError = Result; impl ToipeTui { /// Initializes stdout in raw mode for the TUI. @@ -416,12 +417,14 @@ impl ToipeTui { "Terminal height is too short! Toipe requires at least {} lines, got {} lines", lines.len() + self.bottom_lines_len + 2, terminal_height, - ))); + )) + .into()); } else if max_word_len > terminal_width as usize { return Err(ToipeError::from(format!( "Terminal width is too low! Toipe requires at least {} columns, got {} columns", max_word_len, terminal_width, - ))); + )) + .into()); } self.track_lines = true; diff --git a/src/wordlists.rs b/src/wordlists.rs index c4463af..edfcfda 100644 --- a/src/wordlists.rs +++ b/src/wordlists.rs @@ -5,7 +5,7 @@ use clap::ArgEnum; /// Word lists with top English words. /// /// See [variants](#variants) for details on each word list. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum, Debug)] pub enum BuiltInWordlist { /// Source: [wordfrequency.info](https://www.wordfrequency.info/samples.asp) (top 60K lemmas sample). Top250,