choice_string/
lib.rs

1use nom::error::ErrorKind;
2use std::ops::RangeInclusive;
3use std::str::FromStr;
4
5/// Parser-related functions
6mod parser;
7
8/// Error type for errors that may arise during the parsing of choice-strings.
9/// Very simple at the moment, only wrapping a nom [`ErrorKind`]
10#[derive(thiserror::Error, Debug, Clone, PartialEq)]
11#[non_exhaustive]
12pub enum Error {
13    #[error("Invalid token {}", 0)]
14    ParsingFailed(ErrorKind),
15}
16
17/// A parsed selection. Can represent all, none, or some set of ranges and items.
18#[derive(Debug, PartialEq)]
19pub enum Selection {
20    /// All elements are in the selected set
21    All,
22    /// Some elements are in the selected set. The list of selections are in the Vec.
23    Some(Vec<SomeElementType>),
24    /// No elements are in the selected set
25    None,
26}
27
28impl Selection {
29    /// Check if the selection contains an item. Returns true if All, false if None,
30    /// and checks included elements if Some.
31    pub fn contains_item(&self, item: usize) -> bool {
32        match self {
33            Selection::Some(v) => {
34                v.iter()
35                    .any(|element| {
36                        match element {
37                            SomeElementType::Individual(num) => item == *num,
38                            SomeElementType::Range(range) => range.contains(&item),
39                        }
40                    })
41            },
42            Selection::All => true,
43            Selection::None => false,
44        }
45    }
46}
47
48/// A selected element. Can either be an individual item, or a range of items.
49#[derive(Debug, PartialEq)]
50pub enum SomeElementType {
51    Individual(usize),
52    Range(RangeInclusive<usize>),
53}
54
55pub use parser::parse as parse_raw;
56
57/// Parse a choice string input to a [`Selection`]. Additionally reduces the set of ranges to the
58/// minimum representable by using a union operation.
59/// Wrapper for [`str::parse`].
60pub fn parse(input: &str) -> Result<Selection, Error> {
61    input.parse()
62}
63
64impl FromStr for Selection {
65    type Err = Error;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        Ok(match parse_raw(s)? {
69            Selection::Some(v) => Selection::Some(condense_selections(v)),
70            other => other,
71        })
72    }
73}
74
75/// Union the elements together to produce a set of individual elements and ranges that represents
76/// the same set and reduces the amount of elements.
77fn condense_selections(selections: Vec<SomeElementType>) -> Vec<SomeElementType> {
78    let mut union = range_union_find::IntRangeUnionFind::new();
79    selections
80        .iter()
81        .map(|a| match a {
82            SomeElementType::Individual(num) => *num..=*num,
83            SomeElementType::Range(range) => range.clone(),
84        })
85        .filter(|a| !a.is_empty())
86        .try_for_each(|a| union.insert_range(&a))
87        .expect("bad ranges - shouldn't happen is bug");
88
89    let v = union.into_collection::<Vec<_>>();
90    v.into_iter()
91        .map(|a| {
92            if a.start() == a.end() {
93                SomeElementType::Individual(*a.start())
94            } else {
95                SomeElementType::Range(a)
96            }
97        })
98        .collect()
99}
100
101#[cfg(test)]
102mod helper_tests {
103    use super::*;
104
105    #[test]
106    fn selection_contains_item() {
107        assert!(Selection::All.contains_item(6543268));
108        assert!(!Selection::None.contains_item(385188));
109
110        assert!(Selection::Some(vec![SomeElementType::Individual(1)]).contains_item(1));
111        assert!(Selection::Some(vec![SomeElementType::Range(1..=1)]).contains_item(1));
112        assert!(Selection::Some(vec![SomeElementType::Range(1..=3)]).contains_item(1));
113        assert!(Selection::Some(vec![SomeElementType::Range(1..=3)]).contains_item(3));
114
115        let selection = Selection::Some(vec![
116            SomeElementType::Individual(2),
117            SomeElementType::Individual(6),
118            SomeElementType::Range(4..=8),
119        ]);
120        assert!(selection.contains_item(2));
121        assert!(selection.contains_item(6));
122        assert!(selection.contains_item(5));
123        assert!(selection.contains_item(8));
124        assert!(selection.contains_item(4));
125        assert!(!selection.contains_item(3));
126        assert!(!selection.contains_item(9));
127        assert!(!selection.contains_item(1));
128        assert!(!selection.contains_item(3));
129
130    }
131
132    #[test]
133    fn condense_ranges() {
134        let c = condense_selections(vec![
135            SomeElementType::Individual(1),
136            SomeElementType::Individual(3),
137            SomeElementType::Range(5..=9),
138            SomeElementType::Individual(8),
139            SomeElementType::Individual(10),
140        ]);
141
142        assert_eq!(
143            c,
144            vec![
145                SomeElementType::Individual(1),
146                SomeElementType::Individual(3),
147                SomeElementType::Range(5..=10),
148            ]
149        );
150    }
151
152    #[test]
153    fn condense_ranges_more_complex() {
154        let c = condense_selections(vec![
155            SomeElementType::Individual(1),
156            SomeElementType::Individual(3),
157            SomeElementType::Range(5..=9),
158            SomeElementType::Range(11..=20),
159            SomeElementType::Individual(10),
160        ]);
161
162        assert_eq!(
163            c,
164            vec![
165                SomeElementType::Individual(1),
166                SomeElementType::Individual(3),
167                SomeElementType::Range(5..=20),
168            ]
169        );
170    }
171}