#[cfg(test)]
mod tests;
use std::ops::RangeInclusive;
use fancy_regex_macro::regex;
use rand::{rngs::ThreadRng, seq::SliceRandom, Rng};
use crate::{
draft::{replace_classes, Classes, Draft, Rule},
error::Error,
outcome::{validate_test, Validity::*},
};
pub struct Generator {
rng: ThreadRng,
length: RangeInclusive<usize>,
letters: String,
rules: Vec<Rule>,
}
impl Generator {
pub fn new(draft: &Draft, length: RangeInclusive<usize>) -> Result<Self, Error> {
let letters = get_letters(&draft.raw_classes)?;
Ok(Self {
rng: rand::thread_rng(),
length,
letters,
rules: draft.rules.clone(),
})
}
pub fn next(&mut self) -> String {
loop {
let length = self.rng.gen_range(self.length.clone());
let word = random_word(&self.letters, length, &mut self.rng);
if matches!(validate_test(&word, &self.rules), Valid) {
return word;
}
}
}
}
impl Draft {
pub fn generator(&self, length: RangeInclusive<usize>) -> Result<Generator, Error> {
Generator::new(self, length)
}
}
fn random_word(letters: &str, length: usize, rng: &mut ThreadRng) -> String {
let chars: Vec<char> = letters.chars().collect();
let mut word = String::new();
for _ in 0..length {
word.push(*chars.choose(rng).unwrap());
}
word
}
fn get_letters(classes: &Classes) -> Result<String, Error> {
Ok(remove_regex_symbols(&get_any_class(classes)?))
}
fn remove_regex_symbols(pattern: &str) -> String {
regex!(r"[\[\]|?*+{}():]")
.replace_all(pattern, "")
.to_string()
}
fn get_any_class(classes: &Classes) -> Result<String, Error> {
let Some((pattern, line)) = classes.get("_") else {
return Err(Error::MissingAnyClass);
};
let pattern = replace_classes(pattern, classes, *line)?;
Ok(pattern)
}