passgen-rs 0.1.0

Password generator with a regular-expression-like syntax
Documentation
/// Optimize a Pattern for generation.
use crate::pattern::*;
use std::collections::BTreeSet;

type Dependencies = BTreeSet<Special>;

impl Pattern<Parsed> {
    pub fn optimize(self) -> (Pattern<Optimized>, Dependencies) {
        let mut dependencies = Dependencies::new();
        let output = Optimize::optimize(self, &mut dependencies);
        (output, dependencies)
    }
}

pub trait Optimize {
    type Output;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output;
}

impl Optimize for Literal<Parsed> {
    type Output = Literal<Optimized>;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output {
        Self::Output {
            value: self.value.to_string(),
        }
    }
}

impl Optimize for Special<Parsed> {
    type Output = Special<Optimized>;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output {
        dependencies.insert(self.clone());
        match self {
            Self::Wordlist(name) => Self::Output::Wordlist(name),
            Self::Markov(name) => Self::Output::Markov(name),
            Self::Preset(name) => Self::Output::Preset(name),
        }
    }
}

impl Optimize for Set<Parsed> {
    type Output = Set<Optimized>;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output {
        self.into()
    }
}

impl Optimize for Item<Parsed> {
    type Output = Item<Optimized>;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output {
        Self::Output {
            pattern: Optimize::optimize(self.pattern, dependencies),
            repeat: self.repeat,
            optional: self.optional,
        }
    }
}

impl Segment<Optimized> {
    fn is_empty(&self) -> bool {
        self.items.iter().any(|i| !i.is_empty())
    }
}

impl Group<Optimized> {
    fn is_empty(&self) -> bool {
        self.segments.iter().any(|s| !s.is_empty())
    }
}

impl Item<Optimized> {
    fn is_empty(&self) -> bool {
        // empty if item is empty
        if self.repeat == (0..=0) {
            return true;
        }

        match &self.pattern {
            Pattern::Group(group) => group.is_empty(),
            Pattern::Set(_) => false,
            Pattern::Special(_) => false,
            Pattern::Literal(literal) => literal.value.is_empty(),
        }
    }

    fn is_modified(&self) -> bool {
        self.optional || self.repeat != (1..=1)
    }

    fn try_fuse(&mut self, item: Self) -> Result<(), Self> {
        // can't do it if we're optional or repeat
        if self.is_modified() || item.is_modified() {
            return Err(item);
        }

        match (&mut self.pattern, &item.pattern) {
            (Pattern::Literal(left), Pattern::Literal(right)) => {
                left.value.push_str(&right.value);
                Ok(())
            }
            _ => Err(item),
        }
    }

    fn flatten(self) -> impl Iterator<Item = Self> {
        if self.is_modified() {
            // break
        }

        match &self.pattern {
            Pattern::Group(group) if group.segments.len() == 1 => {}
            _ => {}
        }

        std::iter::once(self)
    }

    fn simplify(self) -> Self {
        if self.is_modified() {
            return self;
        }

        match &self.pattern {
            Pattern::Set(set) => {
                // TODO: set([a]) -> literal('a') (maybe move to flatten?)
                self
            }
            _ => self,
        }
    }
}

impl Optimize for Segment<Parsed> {
    type Output = Segment<Optimized>;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output {
        Self::Output {
            items: self
                .items
                .into_iter()
                .map(|item| item.optimize(dependencies))
                // TODO: simpliy (set([a]) -> literal(a))
                .map(Item::simplify)
                // filter empty items
                .filter(|i| !i.is_empty())
                // try to flatten
                .flat_map(Item::flatten)
                // try to fuse items
                .fold(Vec::new(), |mut items, item| {
                    if let Some(last) = items.last_mut() {
                        if let Err(item) = last.try_fuse(item) {
                            items.push(item);
                        }
                    } else {
                        items.push(item);
                    }
                    items
                }),
        }
    }
}

impl Optimize for Group<Parsed> {
    type Output = Group<Optimized>;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output {
        Self::Output {
            segments: self
                .segments
                .into_iter()
                .map(|segment| segment.optimize(dependencies))
                .collect(),
        }
    }
}

impl Optimize for Pattern<Parsed> {
    type Output = Pattern<Optimized>;

    fn optimize(self, dependencies: &mut Dependencies) -> Self::Output {
        match self {
            Self::Group(group) => Self::Output::Group(group.optimize(dependencies)),
            Self::Set(set) => Self::Output::Set(set.optimize(dependencies)),
            Self::Literal(literal) => Self::Output::Literal(literal.optimize(dependencies)),
            Self::Special(special) => Self::Output::Special(special.optimize(dependencies)),
        }
    }
}