From cda9d629dce769a1c47ecfc0138d67943438a3e4 Mon Sep 17 00:00:00 2001 From: James Duot Date: Sun, 3 Mar 2024 00:52:46 -0800 Subject: [PATCH] feat: read words sequentially This change will read words in a text file and will not scramble it. The `-s` (sequential) flag must be provided in combination with `-f`. Limitations: - `-n` must be provided for long texts (over default count) - files with too many lines will not fit the screen - there's no scrolling yet - word/character filtering may need to be tweaked --- src/config.rs | 4 ++++ src/lib.rs | 15 +++++++++++---- src/textgen.rs | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 957e693..8922a9a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,6 +30,10 @@ 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, } impl ToipeConfig { diff --git a/src/lib.rs b/src/lib.rs index 77458c5..a276c4f 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::{RawWordSelector, WordSelector}; +use textgen::{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 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 385d9f6..8d605eb 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -1,6 +1,7 @@ //! Utilities for generating/selecting new (random) words for the typing //! test. +use std::collections::VecDeque; use std::fs::File; use std::io::{self, BufRead, BufReader, Cursor, Seek, SeekFrom}; use std::path::PathBuf; @@ -248,3 +249,38 @@ impl WordSelector for RawWordSelector { Ok(word) } } + +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", + )), + } + } +}