Skip to content

Commit

Permalink
Merge pull request #40 from Samyak2/better-errors
Browse files Browse the repository at this point in the history
Better errors using `anyhow`
  • Loading branch information
Samyak2 committed Feb 4, 2024
2 parents 777de63 + 4e94a0b commit a52ad70
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 41 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.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ 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"] }
include-flate = {version ="0.1.4", features=["stable"]}
rand = "0.8.4"
termion = "1.5.6"
include-flate = {version ="0.1.4", features=["stable"]}
78 changes: 46 additions & 32 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,21 +40,17 @@ pub struct Toipe {
}

/// Represents any error caught in Toipe.
#[derive(Debug)]
pub struct ToipeError {
/// Error message. Should not start with "error" or similar.
pub msg: String,
}

/// 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 ToipeError {
/// Prefixes the message with a context
pub fn with_context(mut self, context: &str) -> Self {
self.msg = context.to_owned() + &self.msg;
self
}
}

Expand All @@ -62,34 +60,50 @@ impl From<String> for ToipeError {
}
}

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: Box<dyn WordSelector> =
if let Some(wordlist_path) = config.wordlist_file.clone() {
Box::new(RawWordSelector::from_path(PathBuf::from(wordlist_path))?)
} else if let Some(word_list) = config.wordlist.contents() {
Box::new(RawWordSelector::from_string(word_list.to_string())?)
} else if let BuiltInWordlist::OS = config.wordlist {
Box::new(RawWordSelector::from_path(PathBuf::from(OS_WORDLIST_PATH))?)
} else {
// this should never happen!
// TODO: somehow enforce this at compile time?
return Err(ToipeError {
msg: "Undefined word list or path.".to_string(),
});
};
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!("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!("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!(
"reading from the OS wordlist at path '{}'. See https://en.wikipedia.org/wiki/Words_(Unix) for more info on this file and how it can be installed.",
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 @@ -108,7 +122,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 @@ -125,7 +139,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 @@ -137,7 +151,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 @@ -174,7 +188,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 @@ -287,7 +301,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 @@ -14,7 +14,7 @@ flate!(static TOP_MISSPELLED: str from "src/word_lists/commonly_misspelled");
/// 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 a52ad70

Please sign in to comment.