Skip to content

Commit

Permalink
Merge pull request #61 from gabevenberg/punctuation
Browse files Browse the repository at this point in the history
Add option to add punctuation.
  • Loading branch information
Samyak2 authored Feb 26, 2024
2 parents b2ac314 + 1d9efc9 commit e1e0763
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 2 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
3 changes: 3 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 9 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -76,7 +76,7 @@ impl<'a> Toipe {
/// Initializes the word selector.
/// Also invokes [`Toipe::restart()`].
pub fn new(config: ToipeConfig) -> Result<Self> {
let word_selector: Box<dyn WordSelector> = if let Some(wordlist_path) =
let mut word_selector: Box<dyn WordSelector> = if let Some(wordlist_path) =
config.wordlist_file.clone()
{
Box::new(
Expand Down Expand Up @@ -105,6 +105,13 @@ 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(),
Expand Down
90 changes: 90 additions & 0 deletions src/textgen.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! 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;

use rand::seq::SliceRandom;
use rand::Rng;

use bisection::bisect_right;
Expand Down Expand Up @@ -248,3 +250,91 @@ impl<T: Seek + io::Read> WordSelector for RawWordSelector<T> {
Ok(word)
}
}

/// 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<dyn WordSelector>,
next_is_capital: bool,
punctuation_chance: 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 PunctuatedWordSelector {
/// Creates a PunctuatedWordSelector from another WordSelector, allowing the selection of the
/// chance of punctuation.
pub fn from_word_selector(
word_selector: Box<dyn WordSelector>,
punctuation_chance: f64,
) -> Self {
Self {
selector: word_selector,
next_is_capital: true,
punctuation_chance,
}
}
}

impl WordSelector for PunctuatedWordSelector {
fn new_word(&mut self) -> Result<String, io::Error> {
let mut rng = rand::thread_rng();

let mut word = self.selector.new_word()?;

let will_punctuate = rng.gen_bool(self.punctuation_chance);
if will_punctuate || self.next_is_capital {
let mut chars: VecDeque<char> = 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()
{
chars.push_front(c)
}
self.next_is_capital = false;
}
if will_punctuate {
match PUNCTUATION
.choose(&mut rng)
.expect("only returns none if the slice is empty")
{
PunctuationType::Capitaizing(c) => {
self.next_is_capital = true;
chars.push_back(*c)
}
PunctuationType::Ending(c) => chars.push_back(*c),
PunctuationType::Surrounding(opening, closing) => {
chars.push_front(*opening);
chars.push_back(*closing);
}
}
}
word = chars.into_iter().collect();
}
Ok(word)
}
}

0 comments on commit e1e0763

Please sign in to comment.