passgen-rs 0.1.0

Password generator with a regular-expression-like syntax
Documentation
use crate::{Markov, Wordlist};
use rangemap::RangeInclusiveMap;
use std::{fmt::Debug, ops::RangeInclusive, sync::Arc};

pub trait State {
    type Wordlist: Clone + Debug + PartialEq;
    type Markov: Clone + Debug + PartialEq;
    type Preset: Clone + Debug + PartialEq;
    type Literal: Clone + Debug + PartialEq;
    type Characters: Clone + Debug + PartialEq;
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Parsed;

#[derive(Debug, Clone, PartialEq, Default)]
pub struct Optimized;

#[derive(Debug, Clone, PartialEq, Default)]
pub struct Prepared;

impl State for Parsed {
    type Wordlist = String;
    type Markov = String;
    type Preset = String;
    type Literal = char;
    type Characters = RangeInclusiveMap<u32, usize>;
}

impl State for Optimized {
    type Wordlist = String;
    type Markov = String;
    type Preset = String;
    type Literal = String;
    type Characters = RangeInclusiveMap<u32, usize>;
}

impl State for Prepared {
    type Wordlist = Arc<Wordlist>;
    type Markov = Arc<Markov>;
    type Preset = String;
    type Literal = String;
    type Characters = RangeInclusiveMap<u32, usize>;
}

#[derive(Debug, Clone, PartialEq)]
pub enum Pattern<S: State = Parsed> {
    Group(Group<S>),
    Set(Set<S>),
    Special(Special<S>),
    Literal(Literal<S>),
}

macro_rules! impl_from {
    ($type:ident) => {
        impl<S: State> From<$type<S>> for Pattern<S> {
            fn from(group: $type<S>) -> Self {
                Self::$type(group)
            }
        }
    };
}

impl_from!(Group);
impl_from!(Literal);
impl_from!(Set);
impl_from!(Special);

#[derive(Debug, Clone, PartialEq)]
pub struct Literal<S: State = Parsed> {
    pub value: S::Literal,
}

impl Literal {
    pub fn new(value: char) -> Self {
        Self { value }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Item<S: State = Parsed> {
    pub pattern: Pattern<S>,
    pub repeat: RangeInclusive<usize>,
    pub optional: bool,
}

impl Item {
    pub fn new<T: Into<Pattern>>(pattern: T) -> Self {
        Self {
            pattern: pattern.into(),
            repeat: 1..=1,
            optional: false,
        }
    }

    pub fn optional(mut self, optional: bool) -> Self {
        self.optional = optional;
        self
    }

    pub fn repeat(mut self, repeat: RangeInclusive<usize>) -> Self {
        self.repeat = repeat;
        self
    }
}

#[derive(Debug, Clone, PartialEq, Default)]
pub struct Segment<S: State = Parsed> {
    pub items: Vec<Item<S>>,
}

#[derive(Debug, Clone, PartialEq, Default)]
pub struct Group<S: State = Parsed> {
    pub segments: Vec<Segment<S>>,
}

#[derive(Debug, Clone, PartialEq, Default)]
pub struct Set<S: State = Parsed> {
    characters: S::Characters,
    total: usize,
}

impl From<Set<Parsed>> for Set<Optimized> {
    fn from(set: Set<Parsed>) -> Self {
        Self {
            characters: set.characters,
            total: set.total,
        }
    }
}

impl From<Set<Optimized>> for Set<Prepared> {
    fn from(set: Set<Optimized>) -> Self {
        Self {
            characters: set.characters,
            total: set.total,
        }
    }
}

impl Set {
    pub fn insert(&mut self, range: RangeInclusive<char>, count: usize) {
        let range = (*range.start() as u32)..=(*range.end() as u32);
        let length = range.end() + 1 - range.start();

        let overlapping = self
            .characters
            .overlapping(&range)
            .map(|(range, value)| (range.clone(), *value))
            .collect::<Vec<_>>();
        for (subrange, previous) in overlapping.into_iter() {
            let subrange =
                (*subrange.start().max(range.start()))..=(*subrange.end().min(range.end()));
            self.characters.insert(subrange, previous + 1);
        }

        let gaps = self.characters.gaps(&range).collect::<Vec<_>>();
        for range in gaps.into_iter() {
            self.characters.insert(range, 1);
        }

        self.total += length as usize * count;
    }
}

impl Set<Prepared> {
    pub fn total(&self) -> usize {
        self.total
    }

    pub fn characters(&self) -> impl Iterator<Item = (RangeInclusive<char>, usize)> + '_ {
        self.characters.iter().map(|(range, count)| {
            let start = char::from_u32(*range.start()).unwrap();
            let end = char::from_u32(*range.end()).unwrap();
            (start..=end, *count)
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Special<S: State = Parsed> {
    Wordlist(S::Wordlist),
    Markov(S::Markov),
    Preset(S::Preset),
}

#[cfg(test)]
mod tests {
    use super::*;
    use test_strategy::*;

    /// Make sure that we count the total correctly.
    #[proptest]
    fn test_set_total(ranges: Vec<(RangeInclusive<char>, u32)>) {
        // calculate total independently from ranges
        let total = ranges
            .iter()
            .map(|(r, count)| *count as usize * (*r.end() as usize + 1 - *r.start() as usize))
            .sum();

        // load into set
        let mut set = Set::<Parsed>::default();
        ranges
            .into_iter()
            .for_each(|(range, count)| set.insert(range, count as usize));

        // total in set should match calculated value
        assert_eq!(set.total, total);
    }

    /// Make sure that we track the total per-character, correctly in set.
    #[proptest]
    fn test_set_counts(ranges: Vec<(RangeInclusive<char>, u32)>) {
        let total = ranges
            .iter()
            .map(|(r, count)| *count as usize * (*r.end() as usize + 1 - *r.start() as usize))
            .sum();
        let mut set = Set::<Parsed>::default();
        ranges
            .into_iter()
            .for_each(|(range, count)| set.insert(range, count as usize));
        assert_eq!(set.total, total);
    }
}