use std::iter::Peekable;
use std::str::Chars;
use anyhow::Result;
use rand::prelude::*;
use rand::thread_rng;
pub fn generate_random_string(pattern: &str) -> Result<String> {
let mut rng = thread_rng();
let mut result = String::new();
let mut pattern_chars = pattern.chars().peekable();
while let Some(c) = pattern_chars.next() {
match c {
'\\' => {
if let Some(next_char) = pattern_chars.next() {
match next_char {
'd' => {
let repetitions = parse_repetition(&mut pattern_chars);
for _ in 0..repetitions {
result.push(random_digit(&mut rng));
}
}
'w' => {
let repetitions = parse_repetition(&mut pattern_chars);
for _ in 0..repetitions {
let random_char = match rng.gen_range(0..2) {
0 => random_lowercase_letter(&mut rng),
_ => random_uppercase_letter(&mut rng),
};
result.push(random_char);
}
}
's' => {
let repetitions = parse_repetition(&mut pattern_chars);
for _ in 0..repetitions {
result.push(random_special_character(&mut rng));
}
}
_ => {
result.push(next_char);
}
}
}
else {
result.push('\\');
}
}
'.' => {
let repetitions = parse_repetition(&mut pattern_chars);
for _ in 0..repetitions {
let random_char = match rng.gen_range(0..3) {
0 => random_digit(&mut rng),
1 => match rng.gen_range(0..2) {
0 => random_uppercase_letter(&mut rng),
_ => random_lowercase_letter(&mut rng),
},
_ => random_special_character(&mut rng),
};
result.push(random_char);
}
}
unparsed_char => {
result.push(unparsed_char);
}
}
}
Ok(result)
}
fn parse_repetition(pattern_chars: &mut Peekable<Chars>) -> usize {
if pattern_chars.peek() != Some(&'{') {
return 1;
}
let mut repeat_str = String::new();
while let Some(&next) = pattern_chars.peek() {
if next == '}' {
pattern_chars.next();
break;
}
if next == '{' {
pattern_chars.next();
continue;
}
repeat_str.push(next.clone());
pattern_chars.next();
}
let count = repeat_str.parse::<usize>().unwrap_or(1);
count
}
fn random_digit(rng: &mut ThreadRng) -> char {
char::from_u32(rng.gen_range(('0' as u32)..=('9' as u32))).unwrap()
}
fn random_uppercase_letter(rng: &mut ThreadRng) -> char {
char::from_u32(rng.gen_range(('A' as u32)..=('Z' as u32))).unwrap()
}
fn random_lowercase_letter(rng: &mut ThreadRng) -> char {
char::from_u32(rng.gen_range(('a' as u32)..=('z' as u32))).unwrap()
}
fn random_special_character(rng: &mut ThreadRng) -> char {
let default_char_map = "!#$%&()*+,-/:;?<=>@[]^_~.";
default_char_map.chars().choose(rng).unwrap()
}
pub fn generate_password(length: u32) -> Result<String> {
let result = generate_random_string(format!(".{{{length}}}").as_str())?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_random_string() -> Result<(), Box<dyn std::error::Error>> {
let pattern = r"\d{3}\w{3}\s{3}.{3}\{sample\}";
let result = generate_random_string(pattern)?;
println!("Generated random string: {}", result);
assert_eq!(result.len(), 20);
assert_eq!(result.chars().nth(0).unwrap().is_ascii_digit(), true);
assert_eq!(result.chars().nth(1).unwrap().is_ascii_digit(), true);
assert_eq!(result.chars().nth(2).unwrap().is_ascii_digit(), true);
assert_eq!(result.chars().nth(3).unwrap().is_ascii_alphabetic(), true);
assert_eq!(result.chars().nth(4).unwrap().is_ascii_alphabetic(), true);
assert_eq!(result.chars().nth(5).unwrap().is_ascii_alphabetic(), true);
assert_eq!(result.ends_with("{sample}"), true);
let pattern = r"\w\d{2}";
let result = generate_random_string(pattern)?;
println!("Generated random string: {}", result);
assert_eq!(result.len(), 3);
assert_eq!(result.chars().nth(0).unwrap().is_ascii_alphabetic(), true);
assert_eq!(result.chars().nth(1).unwrap().is_ascii_digit(), true);
assert_eq!(result.chars().nth(2).unwrap().is_ascii_digit(), true);
Ok(())
}
#[test]
fn test_generate_password() -> Result<(), Box<dyn std::error::Error>> {
let length = 8;
let result = generate_password(length)?;
println!("Generated 8 char password: {}", result);
assert_eq!(result.len(), length as usize);
Ok(())
}
}