Skip to content

Commit

Permalink
refactor: use anyhow for error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Samyak2 committed Jun 25, 2022
1 parent 30250b5 commit 31ae303
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 49 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
86 changes: 45 additions & 41 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -38,6 +40,7 @@ pub struct Toipe {
}

/// Represents any error caught in Toipe.
#[derive(Debug)]
pub struct ToipeError {
pub msg: String,
}
Expand All @@ -50,60 +53,61 @@ 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<std::io::Error> for ToipeError {
fn from(error: std::io::Error) -> Self {
ToipeError {
msg: error.to_string(),
}
}
}

impl From<String> 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.
///
/// See [`ToipeConfig`] for configuration options.
///
/// Initializes the word selector.
/// Also invokes [`Toipe::restart()`].
pub fn new(config: ToipeConfig) -> Result<Self, ToipeError> {
let word_selector: Result<Box<dyn WordSelector>, _> =
if let Some(wordlist_path) = config.wordlist_file.clone() {
RawWordSelector::from_path(PathBuf::from(wordlist_path))
.map(|ws| Box::new(ws) as Box<dyn WordSelector>)
} else if let Some(word_list) = config.wordlist.contents() {
RawWordSelector::from_string(word_list.to_string())
.map(|ws| Box::new(ws) as Box<dyn WordSelector>)
} else if let BuiltInWordlist::OS = config.wordlist {
RawWordSelector::from_path(PathBuf::from(OS_WORDLIST_PATH))
.map(|ws| Box::new(ws) as Box<dyn WordSelector>)
} 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<Self> {
let word_selector: Box<dyn WordSelector> = 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(),
Expand All @@ -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)?;
Expand All @@ -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(())
}
Expand All @@ -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::<char>::new();
let original_text = self
.text
Expand Down Expand Up @@ -188,7 +192,7 @@ impl<'a> Toipe {
}
}

let mut process_key = |key: Key| -> Result<TestStatus, ToipeError> {
let mut process_key = |key: Key| -> Result<TestStatus> {
match key {
Key::Ctrl('c') => {
return Ok(TestStatus::Quit);
Expand Down Expand Up @@ -301,7 +305,7 @@ impl<'a> Toipe {
&mut self,
results: ToipeResults,
mut keys: Keys<StdinLock>,
) -> Result<bool, ToipeError> {
) -> Result<bool> {
self.tui.reset_screen()?;

self.tui.display_lines::<&[Text], _>(&[
Expand Down
5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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)?;
Expand Down
9 changes: 6 additions & 3 deletions src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use termion::{
};

use crate::ToipeError;
use anyhow::Result;

const MIN_LINE_WIDTH: usize = 50;

Expand Down Expand Up @@ -228,7 +229,7 @@ pub struct ToipeTui {
bottom_lines_len: usize,
}

type MaybeError<T = ()> = Result<T, ToipeError>;
type MaybeError<T = ()> = Result<T>;

impl ToipeTui {
/// Initializes stdout in raw mode for the TUI.
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/wordlists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 31ae303

Please sign in to comment.