diff --git a/src/config.rs b/src/config.rs index 2ea82df..76b231a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,9 +30,16 @@ pub struct ToipeConfig { /// Number of words to show on each test. #[clap(short, long, default_value_t = 30)] pub num_words: usize, + + + /// Read full text sequentially + #[clap(short = 's', long = "sequential", conflicts_with = "wordlist")] + pub use_sequential_words: bool, + /// Whether to include punctuation #[clap(short, long)] pub punctuation: bool, + } impl ToipeConfig { diff --git a/src/lib.rs b/src/lib.rs index 553994a..5174bbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ use config::ToipeConfig; use results::ToipeResults; use termion::input::Keys; use termion::{color, event::Key, input::TermRead}; -use textgen::{PunctuatedWordSelector, RawWordSelector, WordSelector}; +use textgen::{PunctuatedWordSelector, RawWordSelector, SequentialFileWordSelector, WordSelector}; use tui::{Text, ToipeTui}; use wordlists::{BuiltInWordlist, OS_WORDLIST_PATH}; @@ -76,9 +76,16 @@ impl<'a> Toipe { /// Initializes the word selector. /// Also invokes [`Toipe::restart()`]. pub fn new(config: ToipeConfig) -> Result { - let mut word_selector: Box = if let Some(wordlist_path) = - config.wordlist_file.clone() - { + let word_selector: Box = if config.use_sequential_words { + if let Some(wordlist_path) = config.wordlist_file.clone() { + Box::new( + SequentialFileWordSelector::from_path(PathBuf::from(wordlist_path)) + .with_context(|| format!("reading words from path"))?, + ) + } else { + return Err(ToipeError::from("Undefined path.".to_owned()))?; + } + } else 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), diff --git a/src/textgen.rs b/src/textgen.rs index e6e67e6..672f5e7 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -251,6 +251,39 @@ impl WordSelector for RawWordSelector { } } +pub struct SequentialFileWordSelector { + words: VecDeque, +} + +impl SequentialFileWordSelector { + pub fn from_path(path: PathBuf) -> Result { + let file = File::open(&path)?; + let reader = BufReader::new(file); + let words: VecDeque = reader + .lines() + .filter_map(|line| line.ok()) + .flat_map(|line| { + let words: Vec = line.split_whitespace().map(String::from).collect(); + words.into_iter().filter(|word| { + word.chars() + .all(|c| c.is_ascii() && c.is_alphanumeric() && c.is_ascii_graphic()) + }) + }) + .collect(); + Ok(Self { words }) + } +} + +impl WordSelector for SequentialFileWordSelector { + fn new_word(&mut self) -> Result { + match self.words.pop_front() { + Some(word) => Ok(word), + None => Err(io::Error::new( + io::ErrorKind::Other, + "No more words available", + )), + } + /// Wraps another word selector, taking words from it and adding punctuation to the end of or /// around words with a configurable chance. Will capitalize the next word when an end-of-sentence /// punctuation mark is used.