Skip to main content

pants_gen/
interval.rs

1use std::str::FromStr;
2
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct Interval {
7    pub min: usize,
8    pub max: usize,
9}
10
11impl Interval {
12    pub fn new(min: usize, max: usize) -> Option<Self> {
13        if min <= max {
14            Some(Self { min, max })
15        } else {
16            None
17        }
18    }
19
20    pub fn at_least(size: usize) -> Self {
21        Self {
22            min: size,
23            max: usize::MAX,
24        }
25    }
26
27    pub fn at_most(size: usize) -> Self {
28        Self {
29            min: usize::MIN,
30            max: size,
31        }
32    }
33
34    pub fn exactly(size: usize) -> Self {
35        Self {
36            min: size,
37            max: size,
38        }
39    }
40
41    pub fn safe(a: usize, b: usize) -> Self {
42        if a <= b {
43            Self { min: a, max: b }
44        } else {
45            Self { min: b, max: a }
46        }
47    }
48}
49
50#[derive(Debug, Error)]
51pub enum IntervalParseError {
52    #[error("Expect the interval to have the first value <= the second, got {0} <= {1}")]
53    BadBounds(usize, usize),
54    #[error("got `{0}`, expect the format for an interval to be: N, N+, N-, or A-B")]
55    ImproperFormat(String),
56}
57
58impl FromStr for Interval {
59    type Err = IntervalParseError;
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        enum Style {
62            Exact,
63            AtLeast,
64            AtMost,
65            Range,
66        }
67        let s = s.trim();
68        let mut first = vec![];
69        let mut second = vec![];
70        let mut style = Style::Exact;
71        for (i, c) in s.chars().enumerate() {
72            if matches!(style, Style::Exact) {
73                if c.is_numeric() {
74                    first.push(c);
75                } else if c == '+' && i + 1 == s.len() {
76                    style = Style::AtLeast;
77                } else if c == '-' && i + 1 == s.len() {
78                    style = Style::AtMost;
79                } else if c == '-' {
80                    style = Style::Range;
81                } else {
82                    return Err(IntervalParseError::ImproperFormat(s.to_string()));
83                }
84            } else if matches!(style, Style::Range) {
85                if c.is_numeric() {
86                    second.push(c);
87                } else {
88                    return Err(IntervalParseError::ImproperFormat(s.to_string()));
89                }
90            }
91        }
92        match style {
93            Style::Exact => {
94                let n = first
95                    .into_iter()
96                    .collect::<String>()
97                    .parse()
98                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
99                Ok(Interval { min: n, max: n })
100            }
101            Style::AtLeast => {
102                let n = first
103                    .into_iter()
104                    .collect::<String>()
105                    .parse()
106                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
107                Ok(Interval {
108                    min: n,
109                    max: usize::MAX,
110                })
111            }
112            Style::AtMost => {
113                let n = first
114                    .into_iter()
115                    .collect::<String>()
116                    .parse()
117                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
118                Ok(Interval {
119                    min: usize::MIN,
120                    max: n,
121                })
122            }
123            Style::Range => {
124                let a = first
125                    .into_iter()
126                    .collect::<String>()
127                    .parse()
128                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
129                let b = second
130                    .into_iter()
131                    .collect::<String>()
132                    .parse()
133                    .map_err(|_| IntervalParseError::ImproperFormat(s.to_string()))?;
134                if a <= b {
135                    Ok(Interval { min: a, max: b })
136                } else {
137                    Err(IntervalParseError::BadBounds(a, b))
138                }
139            }
140        }
141    }
142}