From 26f1d5963856b59e1bc00dfb0b00085992525167 Mon Sep 17 00:00:00 2001 From: Gabe Venberg Date: Wed, 14 Feb 2024 20:04:06 -0600 Subject: [PATCH 1/6] iterator based implementation. --- src/textgen.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/textgen.rs b/src/textgen.rs index 385d9f6..20fbb80 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -3,8 +3,10 @@ use std::fs::File; use std::io::{self, BufRead, BufReader, Cursor, Seek, SeekFrom}; +use std::iter::{empty, once}; use std::path::PathBuf; +use rand::seq::SliceRandom; use rand::Rng; use bisection::bisect_right; @@ -248,3 +250,66 @@ impl WordSelector for RawWordSelector { Ok(word) } } + +/// Wraps another word selector, taking words from it and sometimes adding punctuation to the end +/// of or around words. Will capitalize the next word when needed. +pub struct PunctuatedWordSelector { + selector: Box, + next_is_capital: bool, + chance_of_punctuation: f64, +} + +enum PunctuationType { + Capitaizing(char), + Ending(char), + Surrounding(char, char), +} + +const PUNCTUATION: [PunctuationType; 12] = [ + PunctuationType::Capitaizing('!'), + PunctuationType::Capitaizing('?'), + PunctuationType::Capitaizing('.'), + PunctuationType::Ending(','), + PunctuationType::Ending(':'), + PunctuationType::Ending(';'), + PunctuationType::Surrounding('\'', '\''), + PunctuationType::Surrounding('"', '"'), + PunctuationType::Surrounding('(', ')'), + PunctuationType::Surrounding('{', '}'), + PunctuationType::Surrounding('<', '>'), + PunctuationType::Surrounding('[', ']'), +]; + +impl WordSelector for PunctuatedWordSelector { + fn new_word(&mut self) -> Result { + let mut rng = rand::thread_rng(); + + let mut word = self.selector.new_word()?; + + let will_punctuate = rng.gen_bool(self.chance_of_punctuation); + if will_punctuate || self.next_is_capital { + let mut chars: Box> = Box::new(word.chars()); + if self.next_is_capital { + chars = match chars.next() { + Some(c) => Box::new(c.to_uppercase().chain(chars)), + None => Box::new(empty()), + } + } + if will_punctuate { + chars = match PUNCTUATION + .choose(&mut rng) + .expect("only returns none if the slice is empty") + { + PunctuationType::Capitaizing(c) => { + self.next_is_capital = true; + Box::new(chars.chain(once(*c))) + } + PunctuationType::Ending(c) => Box::new(chars.chain(once(*c))), + PunctuationType::Surrounding(opening, closing) => Box::new(once(*opening).chain(chars).chain(once(*closing))), + } + } + word = chars.collect(); + } + Ok(word) + } +} From ddc20bb4e831b53857bf3a7d0ef7a45b8da3d2da Mon Sep 17 00:00:00 2001 From: Gabe Venberg Date: Wed, 14 Feb 2024 20:14:01 -0600 Subject: [PATCH 2/6] better version based on double ended vec. --- src/textgen.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/textgen.rs b/src/textgen.rs index 20fbb80..7e5f620 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -1,9 +1,9 @@ //! 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::iter::{empty, once}; use std::path::PathBuf; use rand::seq::SliceRandom; @@ -288,27 +288,31 @@ impl WordSelector for PunctuatedWordSelector { let will_punctuate = rng.gen_bool(self.chance_of_punctuation); if will_punctuate || self.next_is_capital { - let mut chars: Box> = Box::new(word.chars()); + let mut chars: VecDeque = word.chars().collect(); if self.next_is_capital { - chars = match chars.next() { - Some(c) => Box::new(c.to_uppercase().chain(chars)), - None => Box::new(empty()), + // some unicode chars map to multiple chars when uppercased. + for c in chars.pop_front().expect("got empty word").to_uppercase() { + chars.push_front(c) } + self.next_is_capital=false; } if will_punctuate { - chars = match PUNCTUATION + match PUNCTUATION .choose(&mut rng) .expect("only returns none if the slice is empty") { PunctuationType::Capitaizing(c) => { self.next_is_capital = true; - Box::new(chars.chain(once(*c))) + chars.push_back(*c) + } + PunctuationType::Ending(c) => chars.push_back(*c), + PunctuationType::Surrounding(opening, closing) => { + chars.push_front(*opening); + chars.push_back(*closing); } - PunctuationType::Ending(c) => Box::new(chars.chain(once(*c))), - PunctuationType::Surrounding(opening, closing) => Box::new(once(*opening).chain(chars).chain(once(*closing))), } } - word = chars.collect(); + word = chars.into_iter().collect(); } Ok(word) } From 67f9cff12b29b493f43e2a9ddbf6dc2c39b80787 Mon Sep 17 00:00:00 2001 From: Gabe Venberg Date: Wed, 14 Feb 2024 20:26:35 -0600 Subject: [PATCH 3/6] added constructor for punctuatedwordselector --- src/textgen.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/textgen.rs b/src/textgen.rs index 7e5f620..5ffe764 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -251,12 +251,13 @@ impl WordSelector for RawWordSelector { } } -/// Wraps another word selector, taking words from it and sometimes adding punctuation to the end -/// of or around words. Will capitalize the next word when needed. +/// 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. pub struct PunctuatedWordSelector { selector: Box, next_is_capital: bool, - chance_of_punctuation: f64, + punctuation_chance: f64, } enum PunctuationType { @@ -280,13 +281,26 @@ const PUNCTUATION: [PunctuationType; 12] = [ PunctuationType::Surrounding('[', ']'), ]; +impl PunctuatedWordSelector { + pub fn from_word_selector( + word_selector: Box, + punctuation_chance: f64, + ) -> Result { + Ok(Self { + selector: word_selector, + next_is_capital: false, + punctuation_chance, + }) + } +} + impl WordSelector for PunctuatedWordSelector { fn new_word(&mut self) -> Result { let mut rng = rand::thread_rng(); let mut word = self.selector.new_word()?; - let will_punctuate = rng.gen_bool(self.chance_of_punctuation); + let will_punctuate = rng.gen_bool(self.punctuation_chance); if will_punctuate || self.next_is_capital { let mut chars: VecDeque = word.chars().collect(); if self.next_is_capital { @@ -294,7 +308,7 @@ impl WordSelector for PunctuatedWordSelector { for c in chars.pop_front().expect("got empty word").to_uppercase() { chars.push_front(c) } - self.next_is_capital=false; + self.next_is_capital = false; } if will_punctuate { match PUNCTUATION From 0146653380f7e65dbccfe1768b97c44f43827874 Mon Sep 17 00:00:00 2001 From: Gabe Venberg Date: Thu, 15 Feb 2024 11:36:33 -0600 Subject: [PATCH 4/6] added CLI flag, integration with rest of program. --- README.md | 8 ++++++++ src/config.rs | 3 +++ src/lib.rs | 8 ++++++-- src/textgen.rs | 10 ++++++---- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6db4ba6..12e917c 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,14 @@ You can provide your own word list too (Note: the word list must meet [these ass toipe -f /path/to/word/list ``` +## Add punctuation to test + +By default, only lowercase words are shown. To add punctuation and sentence case, use the `-p` flag: + +``` +toipe -p +``` + # Platform support - toipe was only tested on Linux and Mac OS. If you find any problems, please [open an issue](https://github.com/Samyak2/toipe/issues). diff --git a/src/config.rs b/src/config.rs index 957e693..2ea82df 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,6 +30,9 @@ pub struct ToipeConfig { /// Number of words to show on each test. #[clap(short, long, default_value_t = 30)] pub num_words: usize, + /// Whether to include punctuation + #[clap(short, long)] + pub punctuation: bool, } impl ToipeConfig { diff --git a/src/lib.rs b/src/lib.rs index 77458c5..123533f 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::{PunctuatedWordSelector, RawWordSelector, WordSelector}; use tui::{Text, ToipeTui}; use wordlists::{BuiltInWordlist, OS_WORDLIST_PATH}; @@ -76,7 +76,7 @@ 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) = + let mut word_selector: Box = if let Some(wordlist_path) = config.wordlist_file.clone() { Box::new( @@ -105,6 +105,10 @@ impl<'a> Toipe { return Err(ToipeError::from("Undefined word list or path.".to_owned()))?; }; + if config.punctuation { + word_selector = Box::new(PunctuatedWordSelector::from_word_selector(word_selector, 0.15)) + } + let mut toipe = Toipe { tui: ToipeTui::new(), words: Vec::new(), diff --git a/src/textgen.rs b/src/textgen.rs index 5ffe764..2931d1c 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -282,15 +282,17 @@ const PUNCTUATION: [PunctuationType; 12] = [ ]; impl PunctuatedWordSelector { + /// Creates a PunctuatedWordSelector from another WordSelector, allowing the selection of the + /// chance of punctuation. pub fn from_word_selector( word_selector: Box, punctuation_chance: f64, - ) -> Result { - Ok(Self { + ) -> Self { + Self { selector: word_selector, - next_is_capital: false, + next_is_capital: true, punctuation_chance, - }) + } } } From a72c9b7e2583a807dc0f04596b3e9dd21a4f7a1e Mon Sep 17 00:00:00 2001 From: Gabe Venberg Date: Sun, 25 Feb 2024 16:53:14 -0600 Subject: [PATCH 5/6] fixed order of mulit-char uppercase mappings. --- src/textgen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textgen.rs b/src/textgen.rs index 2931d1c..d055b00 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -307,7 +307,7 @@ impl WordSelector for PunctuatedWordSelector { let mut chars: VecDeque = word.chars().collect(); if self.next_is_capital { // some unicode chars map to multiple chars when uppercased. - for c in chars.pop_front().expect("got empty word").to_uppercase() { + for c in chars.pop_front().expect("got empty word").to_uppercase().rev() { chars.push_front(c) } self.next_is_capital = false; From 1d9efc95ba25df2b8a6d8665f8d3241956a563b8 Mon Sep 17 00:00:00 2001 From: Gabe Venberg Date: Sun, 25 Feb 2024 16:54:51 -0600 Subject: [PATCH 6/6] ran cargo fmt. --- src/lib.rs | 5 ++++- src/textgen.rs | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 123533f..553994a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,10 @@ impl<'a> Toipe { }; if config.punctuation { - word_selector = Box::new(PunctuatedWordSelector::from_word_selector(word_selector, 0.15)) + word_selector = Box::new(PunctuatedWordSelector::from_word_selector( + word_selector, + 0.15, + )) } let mut toipe = Toipe { diff --git a/src/textgen.rs b/src/textgen.rs index d055b00..e6e67e6 100644 --- a/src/textgen.rs +++ b/src/textgen.rs @@ -307,7 +307,12 @@ impl WordSelector for PunctuatedWordSelector { let mut chars: VecDeque = word.chars().collect(); if self.next_is_capital { // some unicode chars map to multiple chars when uppercased. - for c in chars.pop_front().expect("got empty word").to_uppercase().rev() { + for c in chars + .pop_front() + .expect("got empty word") + .to_uppercase() + .rev() + { chars.push_front(c) } self.next_is_capital = false;