use std::cell::RefCell;
use rand::prelude::IteratorRandom;
use rand::{Rng, RngCore};
use crate::Randomizer;
const SYMBOLS: &str = r##"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##;
#[derive(Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct StringDef {
pub length: u32,
pub include_unicode: bool,
pub include_symbol: bool,
pub include_capital_letters: bool,
pub include_numbers: bool,
}
impl Default for StringDef {
fn default() -> Self {
Self {
length: 6,
include_unicode: false,
include_symbol: false,
include_capital_letters: false,
include_numbers: false,
}
}
}
pub struct StringDefBuilder<'a> {
pub string_def: StringDef,
pub rng: &'a RefCell<dyn RngCore + Send>,
}
impl StringDefBuilder<'_> {
#[must_use]
pub const fn length(mut self, length: u32) -> Self {
self.string_def.length = length;
self
}
#[must_use]
pub const fn include_unicode(mut self, yes: bool) -> Self {
self.string_def.include_unicode = yes;
self
}
#[must_use]
pub const fn include_symbol(mut self, yes: bool) -> Self {
self.string_def.include_symbol = yes;
self
}
#[must_use]
pub const fn include_capital_letters(mut self, yes: bool) -> Self {
self.string_def.include_capital_letters = yes;
self
}
#[must_use]
pub const fn include_numbers(mut self, yes: bool) -> Self {
self.string_def.include_numbers = yes;
self
}
}
impl std::fmt::Display for StringDefBuilder<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut rng = self.rng.borrow_mut();
let result = self.string_def.generate(&mut *rng);
write!(f, "{result}")
}
}
impl StringDef {
pub fn from_randomizer(randomizer: &Randomizer) -> Self {
Self {
length: randomizer.number_between(1, 50),
include_unicode: randomizer.bool(),
include_symbol: randomizer.bool(),
include_capital_letters: randomizer.bool(),
include_numbers: randomizer.bool(),
}
}
pub fn generate(&self, rng: &mut dyn RngCore) -> String {
let mut result = String::new();
let length: usize = self.length as usize;
while result.len() < length {
let choice: u8 = rng.gen_range(0..100);
if self.include_unicode && choice < 20 {
if let Some(unicode_char) = std::char::from_u32(rng.gen_range(0x1F600..0x1F64F)) {
result.push(unicode_char);
} else {
result.push('?');
}
} else if self.include_symbol && choice < 40 {
if let Some(symbol) = SYMBOLS.chars().choose(rng) {
result.push(symbol);
} else {
result.push('#');
}
} else if self.include_capital_letters && choice < 60 {
let capital_letter = rng.gen_range(b'A'..=b'Z') as char;
result.push(capital_letter);
} else if self.include_numbers && choice < 80 {
let number = rng.gen_range(b'0'..=b'9') as char;
result.push(number);
} else {
let lowercase_letter = rng.gen_range(b'a'..=b'z') as char;
result.push(lowercase_letter);
}
}
result
}
#[must_use]
pub fn contains_only_lowercase(s: &str) -> bool {
!Self::contains_capital_letters(s)
&& !Self::contains_numbers(s)
&& !Self::contains_symbols(s)
&& !Self::contains_unicode(s)
}
#[must_use]
pub fn contains_unicode(s: &str) -> bool {
s.chars().any(|ch| !ch.is_ascii())
}
#[must_use]
pub fn contains_symbols(s: &str) -> bool {
s.chars().any(|ch| SYMBOLS.contains(ch))
}
#[must_use]
pub fn contains_numbers(s: &str) -> bool {
s.chars().any(char::is_numeric)
}
#[must_use]
pub fn contains_capital_letters(s: &str) -> bool {
s.chars().any(char::is_uppercase)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{rngs::StdRng, SeedableRng};
#[test]
fn has_unicode() {
assert!(!StringDef::contains_unicode("test"));
assert!(StringDef::contains_unicode("🙆test"));
}
#[test]
fn has_symbols() {
assert!(!StringDef::contains_symbols("test"));
assert!(StringDef::contains_symbols("test#"));
}
#[test]
fn has_numbers() {
assert!(!StringDef::contains_numbers("test"));
assert!(StringDef::contains_numbers("test1"));
}
#[test]
fn has_capital_letters() {
assert!(!StringDef::contains_capital_letters("test"));
assert!(StringDef::contains_capital_letters("Test1"));
}
#[test]
fn string_def_default() {
let string_def = StringDef::default();
let randomizer = Randomizer::with_seed(42);
let mut rng = randomizer.rng.borrow_mut();
assert_eq!(string_def.generate(&mut *rng), "noqkak");
assert_eq!(string_def.generate(&mut *rng), "twdayn");
assert_eq!(string_def.generate(&mut *rng), "kdnfan");
}
#[test]
fn string_def_with_length() {
let string_def = StringDef {
length: 10,
include_unicode: false,
include_symbol: false,
include_capital_letters: false,
include_numbers: false,
};
let mut rand = Box::new(StdRng::seed_from_u64(42));
assert_eq!(string_def.generate(&mut rand), "noqkaktwda");
assert_eq!(string_def.generate(&mut rand), "ynkdnfanbq");
assert_eq!(string_def.generate(&mut rand), "vmnbjlufkr");
}
#[test]
fn string_def_include_unicode() {
let string_def = StringDef {
length: 6,
include_unicode: true,
include_symbol: false,
include_capital_letters: false,
include_numbers: false,
};
let mut rand = Box::new(StdRng::seed_from_u64(42));
assert_eq!(string_def.generate(&mut rand), "😩oq");
assert_eq!(string_def.generate(&mut rand), "kakt🙃");
assert_eq!(string_def.generate(&mut rand), "daynkd");
}
#[test]
fn string_def_include_symbol() {
let string_def = StringDef {
length: 6,
include_unicode: false,
include_symbol: true,
include_capital_letters: false,
include_numbers: false,
};
let mut rand = Box::new(StdRng::seed_from_u64(42));
assert_eq!(string_def.generate(&mut rand), "\"eq)a)");
assert_eq!(string_def.generate(&mut rand), "=wqf`g");
assert_eq!(string_def.generate(&mut rand), "/uzw=d");
}
#[test]
fn string_def_include_capital_letters() {
let string_def = StringDef {
length: 6,
include_unicode: false,
include_symbol: false,
include_capital_letters: true,
include_numbers: false,
};
let mut rand = Box::new(StdRng::seed_from_u64(42));
assert_eq!(string_def.generate(&mut rand), "NOqkak");
assert_eq!(string_def.generate(&mut rand), "TWdAyN");
assert_eq!(string_def.generate(&mut rand), "kdnfaN");
}
#[test]
fn string_def_include_numbers() {
let string_def = StringDef {
length: 6,
include_unicode: false,
include_symbol: false,
include_capital_letters: false,
include_numbers: true,
};
let mut rand = Box::new(StdRng::seed_from_u64(42));
assert_eq!(string_def.generate(&mut rand), "55qka4");
assert_eq!(string_def.generate(&mut rand), "7810y5");
assert_eq!(string_def.generate(&mut rand), "k1nf05");
}
}