#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
use std::{error, fmt};
use rand::seq::SliceRandom;
static UPPER_ALPHABET: &str =
include_str!(concat!(env!("OUT_DIR"), "/upper.txt"));
static LOWER_ALPHABET: &str =
include_str!(concat!(env!("OUT_DIR"), "/lower.txt"));
static LETTER_ALPHABET: &str = concat!(
include_str!(concat!(env!("OUT_DIR"), "/lower.txt")),
include_str!(concat!(env!("OUT_DIR"), "/upper.txt")),
);
static DIGIT_ALPHABET: &str =
include_str!(concat!(env!("OUT_DIR"), "/digit.txt"));
static SYMBOL_ALPHABET: &str =
include_str!(concat!(env!("OUT_DIR"), "/symbol.txt"));
static WHITESPACE_ALPHABET: &str =
include_str!(concat!(env!("OUT_DIR"), "/whitespace.txt"));
#[derive(Debug)]
pub enum Error {
NoAlphabet,
TooShort,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::NoAlphabet => write!(f, "No alphabet specified"),
Error::TooShort => write!(
f,
"Length is too short to contain all mandatory alphabets"
),
}
}
}
pub struct RandStrBuilder {
upper: Option<&'static str>,
lower: Option<&'static str>,
letter: Option<&'static str>,
digit: Option<&'static str>,
symbol: Option<&'static str>,
whitespace: Option<&'static str>,
custom: Option<String>,
must_upper: bool,
must_lower: bool,
must_letter: bool,
must_digit: bool,
must_symbol: bool,
must_whitespace: bool,
must_custom: bool,
rng: Option<rand::rngs::ThreadRng>,
len: usize,
}
impl RandStrBuilder {
fn new() -> Self {
RandStrBuilder {
upper: None,
lower: None,
letter: None,
digit: None,
symbol: None,
whitespace: None,
custom: None,
must_upper: false,
must_lower: false,
must_letter: false,
must_digit: false,
must_symbol: false,
must_whitespace: false,
must_custom: false,
rng: None,
len: 0,
}
}
pub fn upper(&mut self) -> &mut Self {
self.upper = Some(UPPER_ALPHABET);
self
}
pub fn lower(&mut self) -> &mut Self {
self.lower = Some(LOWER_ALPHABET);
self
}
pub fn letter(&mut self) -> &mut Self {
self.letter = Some(LETTER_ALPHABET);
self
}
pub fn whitespace(&mut self) -> &mut Self {
self.whitespace = Some(WHITESPACE_ALPHABET);
self
}
pub fn digit(&mut self) -> &mut Self {
self.digit = Some(DIGIT_ALPHABET);
self
}
pub fn symbol(&mut self) -> &mut Self {
self.symbol = Some(SYMBOL_ALPHABET);
self
}
pub fn custom(&mut self, custom: &str) -> &mut Self {
self.custom = Some(custom.to_string());
self
}
pub fn all(&mut self) -> &mut Self {
self.letter().digit().symbol()
}
pub fn must_upper(&mut self) -> &mut Self {
self.must_upper = true;
self.upper()
}
pub fn must_lower(&mut self) -> &mut Self {
self.must_lower = true;
self.lower()
}
pub fn must_letter(&mut self) -> &mut Self {
self.must_letter = true;
self.letter()
}
pub fn must_digit(&mut self) -> &mut Self {
self.must_digit = true;
self.digit()
}
pub fn must_symbol(&mut self) -> &mut Self {
self.must_symbol = true;
self.symbol()
}
pub fn must_whitespace(&mut self) -> &mut Self {
self.must_whitespace = true;
self.symbol()
}
pub fn must_custom(&mut self, custom: &str) -> &mut Self {
self.must_custom = true;
self.custom(custom)
}
pub fn rng(&mut self, rng: rand::rngs::ThreadRng) -> &mut Self {
self.rng = Some(rng);
self
}
pub fn len(&mut self, len: usize) -> &mut Self {
self.len = len;
self
}
pub fn build(&self) -> RandStr {
self.try_build().unwrap()
}
pub fn try_build(&self) -> Result<RandStr, Error> {
let options = self;
let custom = options.custom.as_deref();
let mut alphabet: Vec<_> = [
options.upper,
options.lower,
options.letter,
options.digit,
options.symbol,
options.whitespace,
custom,
]
.into_iter()
.flatten()
.flat_map(|a| a.as_bytes().iter())
.cloned()
.collect();
if alphabet.is_empty() {
Err(Error::NoAlphabet)?;
}
alphabet.sort_unstable();
alphabet.dedup();
let must_alphabets: Vec<_> = [
options
.must_upper
.then(|| options.upper.expect("upper alphabet is not set")),
options
.must_lower
.then(|| options.lower.expect("lower alphabet is not set")),
options
.must_letter
.then(|| options.letter.expect("lower alphabet is not set")),
options
.must_digit
.then(|| options.digit.expect("digit alphabet is not set")),
options.must_whitespace.then(|| {
options.whitespace.expect("whitespace alphabet is not set")
}),
options
.must_symbol
.then(|| options.symbol.expect("symbol alphabet is not set")),
options
.must_custom
.then(|| custom.expect("symbol alphabet is not set")),
]
.into_iter()
.flatten()
.map(|a| a.as_bytes().to_vec())
.collect();
let len = options.len;
if len < must_alphabets.len() {
Err(Error::TooShort)?;
}
let rng = options.rng.clone().unwrap_or_else(rand::thread_rng);
Ok(RandStr {
alphabet,
must_alphabets,
len,
rng,
})
}
}
pub struct RandStr {
alphabet: Vec<u8>,
must_alphabets: Vec<Vec<u8>>,
len: usize,
rng: rand::rngs::ThreadRng,
}
impl RandStr {
pub fn generate(&mut self) -> String {
let capacity = self.len + self.must_alphabets.len();
let mut result = Vec::with_capacity(capacity);
for _ in 0..self.len {
result.push(*self.alphabet.choose(&mut self.rng).unwrap());
}
let mut rearrange = false;
for alpha in &self.must_alphabets {
if !alpha.iter().any(|&a| result.contains(&a)) {
rearrange = true;
result.push(*alpha.choose(&mut self.rng).unwrap());
}
}
if rearrange {
result = result.rchunks(self.len).next().unwrap().to_vec();
result.shuffle(&mut self.rng);
}
String::from_utf8(result).unwrap()
}
}
pub fn randstr() -> RandStrBuilder {
RandStrBuilder::new()
}