Skip to content

Commit a52ad70

Browse files
authored
Merge pull request #40 from Samyak2/better-errors
Better errors using `anyhow`
2 parents 777de63 + 4e94a0b commit a52ad70

File tree

6 files changed

+67
-41
lines changed

6 files changed

+67
-41
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-3
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ strip = "debuginfo"
2222
[lib]
2323

2424
[dependencies]
25-
termion = "1.5.6"
26-
rand = "0.8.4"
25+
anyhow = "1.0"
2726
bisection = "0.1.0"
2827
clap = { version = "3.0.5", features = ["derive", "color", "suggestions"] }
29-
include-flate = {version ="0.1.4", features=["stable"]}
28+
rand = "0.8.4"
29+
termion = "1.5.6"
30+
include-flate = {version ="0.1.4", features=["stable"]}

src/lib.rs

+46-32
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ use textgen::{RawWordSelector, WordSelector};
2828
use tui::{Text, ToipeTui};
2929
use wordlists::{BuiltInWordlist, OS_WORDLIST_PATH};
3030

31+
use anyhow::{Context, Result};
32+
3133
/// Typing test terminal UI and logic.
3234
pub struct Toipe {
3335
tui: ToipeTui,
@@ -38,21 +40,17 @@ pub struct Toipe {
3840
}
3941

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

45-
/// Converts [`std::io::Error`] to [`ToipeError`].
46-
///
47-
/// This keeps only the error message.
48-
///
49-
/// TODO: there must be a better way to keep information from the
50-
/// original error.
51-
impl From<std::io::Error> for ToipeError {
52-
fn from(error: std::io::Error) -> Self {
53-
ToipeError {
54-
msg: error.to_string(),
55-
}
49+
impl ToipeError {
50+
/// Prefixes the message with a context
51+
pub fn with_context(mut self, context: &str) -> Self {
52+
self.msg = context.to_owned() + &self.msg;
53+
self
5654
}
5755
}
5856

@@ -62,34 +60,50 @@ impl From<String> for ToipeError {
6260
}
6361
}
6462

65-
impl std::fmt::Debug for ToipeError {
63+
impl std::fmt::Display for ToipeError {
6664
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6765
f.write_str(format!("ToipeError: {}", self.msg).as_str())
6866
}
6967
}
7068

69+
impl std::error::Error for ToipeError {}
70+
7171
impl<'a> Toipe {
7272
/// Initializes a new typing test on the standard output.
7373
///
7474
/// See [`ToipeConfig`] for configuration options.
7575
///
7676
/// Initializes the word selector.
7777
/// Also invokes [`Toipe::restart()`].
78-
pub fn new(config: ToipeConfig) -> Result<Self, ToipeError> {
79-
let word_selector: Box<dyn WordSelector> =
80-
if let Some(wordlist_path) = config.wordlist_file.clone() {
81-
Box::new(RawWordSelector::from_path(PathBuf::from(wordlist_path))?)
82-
} else if let Some(word_list) = config.wordlist.contents() {
83-
Box::new(RawWordSelector::from_string(word_list.to_string())?)
84-
} else if let BuiltInWordlist::OS = config.wordlist {
85-
Box::new(RawWordSelector::from_path(PathBuf::from(OS_WORDLIST_PATH))?)
86-
} else {
87-
// this should never happen!
88-
// TODO: somehow enforce this at compile time?
89-
return Err(ToipeError {
90-
msg: "Undefined word list or path.".to_string(),
91-
});
92-
};
78+
pub fn new(config: ToipeConfig) -> Result<Self> {
79+
let word_selector: Box<dyn WordSelector> = if let Some(wordlist_path) =
80+
config.wordlist_file.clone()
81+
{
82+
Box::new(
83+
RawWordSelector::from_path(PathBuf::from(wordlist_path.clone())).with_context(
84+
|| format!("reading the word list from given path '{}'", wordlist_path),
85+
)?,
86+
)
87+
} else if let Some(word_list) = config.wordlist.contents() {
88+
Box::new(
89+
RawWordSelector::from_string(word_list.to_string()).with_context(|| {
90+
format!("reading the built-in word list {:?}", config.wordlist)
91+
})?,
92+
)
93+
} else if let BuiltInWordlist::OS = config.wordlist {
94+
Box::new(
95+
RawWordSelector::from_path(PathBuf::from(OS_WORDLIST_PATH)).with_context(|| {
96+
format!(
97+
"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.",
98+
OS_WORDLIST_PATH
99+
)
100+
})?,
101+
)
102+
} else {
103+
// this should never happen!
104+
// TODO: somehow enforce this at compile time?
105+
return Err(ToipeError::from("Undefined word list or path.".to_owned()))?;
106+
};
93107

94108
let mut toipe = Toipe {
95109
tui: ToipeTui::new(),
@@ -108,7 +122,7 @@ impl<'a> Toipe {
108122
///
109123
/// Clears the screen, generates new words and displays them on the
110124
/// UI.
111-
pub fn restart(&mut self) -> Result<(), ToipeError> {
125+
pub fn restart(&mut self) -> Result<()> {
112126
self.tui.reset_screen()?;
113127

114128
self.words = self.word_selector.new_words(self.config.num_words)?;
@@ -125,7 +139,7 @@ impl<'a> Toipe {
125139
Ok(())
126140
}
127141

128-
fn show_words(&mut self) -> Result<(), ToipeError> {
142+
fn show_words(&mut self) -> Result<()> {
129143
self.text = self.tui.display_words(&self.words)?;
130144
Ok(())
131145
}
@@ -137,7 +151,7 @@ impl<'a> Toipe {
137151
/// If the test completes successfully, returns a boolean indicating
138152
/// whether the user wants to do another test and the
139153
/// [`ToipeResults`] for this test.
140-
pub fn test(&mut self, stdin: StdinLock<'a>) -> Result<(bool, ToipeResults), ToipeError> {
154+
pub fn test(&mut self, stdin: StdinLock<'a>) -> Result<(bool, ToipeResults)> {
141155
let mut input = Vec::<char>::new();
142156
let original_text = self
143157
.text
@@ -174,7 +188,7 @@ impl<'a> Toipe {
174188
}
175189
}
176190

177-
let mut process_key = |key: Key| -> Result<TestStatus, ToipeError> {
191+
let mut process_key = |key: Key| -> Result<TestStatus> {
178192
match key {
179193
Key::Ctrl('c') => {
180194
return Ok(TestStatus::Quit);
@@ -287,7 +301,7 @@ impl<'a> Toipe {
287301
&mut self,
288302
results: ToipeResults,
289303
mut keys: Keys<StdinLock>,
290-
) -> Result<bool, ToipeError> {
304+
) -> Result<bool> {
291305
self.tui.reset_screen()?;
292306

293307
self.tui.display_lines::<&[Text], _>(&[

src/main.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use anyhow::Result;
12
use clap::StructOpt;
3+
24
use std::io::stdin;
35
use toipe::config::ToipeConfig;
46
use toipe::Toipe;
5-
use toipe::ToipeError;
67

7-
fn main() -> Result<(), ToipeError> {
8+
fn main() -> Result<()> {
89
let config = ToipeConfig::parse();
910

1011
let mut toipe = Toipe::new(config)?;

src/tui.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use termion::{
1414
};
1515

1616
use crate::ToipeError;
17+
use anyhow::Result;
1718

1819
const MIN_LINE_WIDTH: usize = 50;
1920

@@ -228,7 +229,7 @@ pub struct ToipeTui {
228229
bottom_lines_len: usize,
229230
}
230231

231-
type MaybeError<T = ()> = Result<T, ToipeError>;
232+
type MaybeError<T = ()> = Result<T>;
232233

233234
impl ToipeTui {
234235
/// Initializes stdout in raw mode for the TUI.
@@ -416,12 +417,14 @@ impl ToipeTui {
416417
"Terminal height is too short! Toipe requires at least {} lines, got {} lines",
417418
lines.len() + self.bottom_lines_len + 2,
418419
terminal_height,
419-
)));
420+
))
421+
.into());
420422
} else if max_word_len > terminal_width as usize {
421423
return Err(ToipeError::from(format!(
422424
"Terminal width is too low! Toipe requires at least {} columns, got {} columns",
423425
max_word_len, terminal_width,
424-
)));
426+
))
427+
.into());
425428
}
426429

427430
self.track_lines = true;

src/wordlists.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ flate!(static TOP_MISSPELLED: str from "src/word_lists/commonly_misspelled");
1414
/// Word lists with top English words.
1515
///
1616
/// See [variants](#variants) for details on each word list.
17-
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum)]
17+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum, Debug)]
1818
pub enum BuiltInWordlist {
1919
/// Source: [wordfrequency.info](https://www.wordfrequency.info/samples.asp) (top 60K lemmas sample).
2020
Top250,

0 commit comments

Comments
 (0)