passgen-rs 0.1.0

Password generator with a regular-expression-like syntax
Documentation
use super::pattern::*;
use rand::{Rng, RngCore};
use std::fmt::Write;

type Pattern = super::pattern::Pattern<Prepared>;
type Group = super::pattern::Group<Prepared>;
type Set = super::pattern::Set<Prepared>;
type Segment = super::pattern::Segment<Prepared>;
type Item = super::pattern::Item<Prepared>;
type Special = super::pattern::Special<Prepared>;
type Literal = super::pattern::Literal<Prepared>;

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("abc")]
    Write(#[from] std::fmt::Error),
}

pub trait Generate {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error>;
}

impl Pattern {
    pub fn generate(&self, rng: &mut dyn RngCore) -> Result<String, Error> {
        let mut output = String::new();
        Generate::generate(self, rng, &mut output)?;
        Ok(output)
    }
}

impl Generate for Pattern {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error> {
        match self {
            Pattern::Group(group) => group.generate(rand, output),
            Pattern::Set(set) => set.generate(rand, output),
            Pattern::Literal(literal) => literal.generate(rand, output),
            Pattern::Special(special) => special.generate(rand, output),
        }
    }
}

impl Generate for Group {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error> {
        let index = rand.gen_range(0..self.segments.len());
        self.segments[index].generate(rand, output)
    }
}

impl Generate for Segment {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error> {
        let mut entropy = 1.0;
        for item in &self.items {
            entropy *= item.generate(rand, output)?;
        }

        Ok(entropy)
    }
}

impl Generate for Item {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error> {
        if self.optional && rand.gen::<bool>() {
            return Ok(1.0);
        }

        let amount = rand.gen_range(self.repeat.clone());
        let mut entropy = 1.0;
        for _ in 0..amount {
            entropy *= Generate::generate(&self.pattern, rand, output)?;
        }

        Ok(entropy)
    }
}

impl Generate for Literal {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error> {
        output.write_str(&self.value)?;
        Ok(1.0)
    }
}

impl Generate for Special {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error> {
        match self {
            Self::Wordlist(list) => {
                let mut index = rand.gen_range(0..list.total());
                for (word, count) in list.iter() {
                    if index < count {
                        output.write_str(word)?;
                        return Ok(list.total() as f64 / count as f64);
                    }
                    index -= count;
                }
                unreachable!()
            }
            Self::Markov(markov) => {
                let mut window = markov.window();
                let mut total_entropy = 1f64;
                while let (Some(character), entropy) = markov.generate(rand, &mut window) {
                    output.write_char(character)?;
                    total_entropy *= entropy;
                }
                Ok(total_entropy)
            }
            _ => todo!(),
        }
    }
}

impl Generate for Set {
    fn generate(&self, rand: &mut dyn RngCore, output: &mut dyn Write) -> Result<f64, Error> {
        let mut index = rand.gen_range(0..self.total());
        for (c, count) in self
            .characters()
            .flat_map(|(range, count)| range.map(move |c| (c, count)))
        {
            if index < count {
                output.write_char(c)?;
                return Ok(self.total() as f64 / count as f64);
            }

            index -= count;
        }

        unreachable!()
    }
}