pants-gen 0.3.0

A password generator.
Documentation
use std::str::FromStr;

use thiserror::Error;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interval {
    pub min: usize,
    pub max: usize,
}

impl Interval {
    pub fn new(min: usize, max: usize) -> Option<Self> {
        if min <= max {
            Some(Self { min, max })
        } else {
            None
        }
    }

    pub fn at_least(size: usize) -> Self {
        Self {
            min: size,
            max: usize::MAX,
        }
    }

    pub fn at_most(size: usize) -> Self {
        Self {
            min: usize::MIN,
            max: size,
        }
    }

    pub fn exactly(size: usize) -> Self {
        Self {
            min: size,
            max: size,
        }
    }

    pub fn safe(a: usize, b: usize) -> Self {
        if a <= b {
            Self { min: a, max: b }
        } else {
            Self { min: b, max: a }
        }
    }
}

#[derive(Debug, Error)]
pub enum IntervalParseError {
    #[error("Expect the interval to have the first value <= the second, got {0} <= {1}")]
    BadBounds(usize, usize),
    #[error("got `{0}`, expect the format for an interval to be: N, N+, N-, or A-B")]
    ImproperFormat(String),
}

impl FromStr for Interval {
    type Err = IntervalParseError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        enum Style {
            Exact,
            AtLeast,
            AtMost,
            Range,
        }
        let s = s.trim();
        let mut first = vec![];
        let mut second = vec![];
        let mut style = Style::Exact;
        for (i, c) in s.chars().enumerate() {
            if matches!(style, Style::Exact) {
                if c.is_numeric() {
                    first.push(c);
                } else if c == '+' && i + 1 == s.len() {
                    style = Style::AtLeast;
                } else if c == '-' && i + 1 == s.len() {
                    style = Style::AtMost;
                } else if c == '-' {
                    style = Style::Range;
                } else {
                    return Err(IntervalParseError::ImproperFormat(s.to_string()));
                }
            } else if matches!(style, Style::Range) {
                if c.is_numeric() {
                    second.push(c);
                } else {
                    return Err(IntervalParseError::ImproperFormat(s.to_string()));
                }
            }
        }
        match style {
            Style::Exact => {
                let n = first
                    .into_iter()
                    .collect::<String>()
                    .parse()
                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
                Ok(Interval { min: n, max: n })
            }
            Style::AtLeast => {
                let n = first
                    .into_iter()
                    .collect::<String>()
                    .parse()
                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
                Ok(Interval {
                    min: n,
                    max: usize::MAX,
                })
            }
            Style::AtMost => {
                let n = first
                    .into_iter()
                    .collect::<String>()
                    .parse()
                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
                Ok(Interval {
                    min: usize::MIN,
                    max: n,
                })
            }
            Style::Range => {
                let a = first
                    .into_iter()
                    .collect::<String>()
                    .parse()
                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
                let b = second
                    .into_iter()
                    .collect::<String>()
                    .parse()
                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
                if a <= b {
                    Ok(Interval { min: a, max: b })
                } else {
                    Err(IntervalParseError::BadBounds(a, b))
                }
            }
        }
    }
}