@@ -28,6 +28,8 @@ use textgen::{RawWordSelector, WordSelector};
28
28
use tui:: { Text , ToipeTui } ;
29
29
use wordlists:: { BuiltInWordlist , OS_WORDLIST_PATH } ;
30
30
31
+ use anyhow:: { Context , Result } ;
32
+
31
33
/// Typing test terminal UI and logic.
32
34
pub struct Toipe {
33
35
tui : ToipeTui ,
@@ -38,21 +40,17 @@ pub struct Toipe {
38
40
}
39
41
40
42
/// Represents any error caught in Toipe.
43
+ #[ derive( Debug ) ]
41
44
pub struct ToipeError {
45
+ /// Error message. Should not start with "error" or similar.
42
46
pub msg : String ,
43
47
}
44
48
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
56
54
}
57
55
}
58
56
@@ -62,34 +60,50 @@ impl From<String> for ToipeError {
62
60
}
63
61
}
64
62
65
- impl std:: fmt:: Debug for ToipeError {
63
+ impl std:: fmt:: Display for ToipeError {
66
64
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
67
65
f. write_str ( format ! ( "ToipeError: {}" , self . msg) . as_str ( ) )
68
66
}
69
67
}
70
68
69
+ impl std:: error:: Error for ToipeError { }
70
+
71
71
impl < ' a > Toipe {
72
72
/// Initializes a new typing test on the standard output.
73
73
///
74
74
/// See [`ToipeConfig`] for configuration options.
75
75
///
76
76
/// Initializes the word selector.
77
77
/// 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
+ } ;
93
107
94
108
let mut toipe = Toipe {
95
109
tui : ToipeTui :: new ( ) ,
@@ -108,7 +122,7 @@ impl<'a> Toipe {
108
122
///
109
123
/// Clears the screen, generates new words and displays them on the
110
124
/// UI.
111
- pub fn restart ( & mut self ) -> Result < ( ) , ToipeError > {
125
+ pub fn restart ( & mut self ) -> Result < ( ) > {
112
126
self . tui . reset_screen ( ) ?;
113
127
114
128
self . words = self . word_selector . new_words ( self . config . num_words ) ?;
@@ -125,7 +139,7 @@ impl<'a> Toipe {
125
139
Ok ( ( ) )
126
140
}
127
141
128
- fn show_words ( & mut self ) -> Result < ( ) , ToipeError > {
142
+ fn show_words ( & mut self ) -> Result < ( ) > {
129
143
self . text = self . tui . display_words ( & self . words ) ?;
130
144
Ok ( ( ) )
131
145
}
@@ -137,7 +151,7 @@ impl<'a> Toipe {
137
151
/// If the test completes successfully, returns a boolean indicating
138
152
/// whether the user wants to do another test and the
139
153
/// [`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 ) > {
141
155
let mut input = Vec :: < char > :: new ( ) ;
142
156
let original_text = self
143
157
. text
@@ -174,7 +188,7 @@ impl<'a> Toipe {
174
188
}
175
189
}
176
190
177
- let mut process_key = |key : Key | -> Result < TestStatus , ToipeError > {
191
+ let mut process_key = |key : Key | -> Result < TestStatus > {
178
192
match key {
179
193
Key :: Ctrl ( 'c' ) => {
180
194
return Ok ( TestStatus :: Quit ) ;
@@ -287,7 +301,7 @@ impl<'a> Toipe {
287
301
& mut self ,
288
302
results : ToipeResults ,
289
303
mut keys : Keys < StdinLock > ,
290
- ) -> Result < bool , ToipeError > {
304
+ ) -> Result < bool > {
291
305
self . tui . reset_screen ( ) ?;
292
306
293
307
self . tui . display_lines :: < & [ Text ] , _ > ( & [
0 commit comments