semver_parser/
range_set.rs

1use crate::*;
2use pest::Parser;
3
4use std::str::FromStr;
5
6#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
7pub struct RangeSet {
8    pub ranges: Vec<Range>,
9    pub compat: Compat,
10}
11
12#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
13pub enum Compat {
14    Cargo, // default
15    Npm,
16}
17
18impl RangeSet {
19    fn new() -> RangeSet {
20        RangeSet {
21            ranges: Vec::new(),
22            compat: Compat::Cargo, // default
23        }
24    }
25
26    pub fn parse(input: &str, compat: Compat) -> Result<Self, String> {
27        let range_set = match SemverParser::parse(Rule::range_set, input) {
28            Ok(mut parsed) => match parsed.next() {
29                Some(parsed) => parsed,
30                None => return Err(String::from("Could not parse a range set")),
31            },
32            Err(e) => return Err(e.to_string()),
33        };
34
35        from_pair_iterator(range_set, compat)
36    }
37}
38
39impl FromStr for RangeSet {
40    type Err = String;
41
42    fn from_str(input: &str) -> Result<Self, Self::Err> {
43        // default to cargo-compatible mode
44        RangeSet::parse(input, Compat::Cargo)
45    }
46}
47
48/// Converts an iterator of Pairs into a RangeSet
49fn from_pair_iterator(
50    parsed_range_set: pest::iterators::Pair<'_, Rule>,
51    compat: Compat,
52) -> Result<RangeSet, String> {
53    // First of all, do we have the correct iterator?
54    if parsed_range_set.as_rule() != Rule::range_set {
55        return Err(String::from("Error parsing range set"));
56    }
57
58    // Next, we make a new, empty range
59    let mut range_set = RangeSet::new();
60    range_set.compat = compat;
61
62    // Now we need to parse each range out of the set
63    for record in parsed_range_set.into_inner() {
64        match record.as_rule() {
65            // if we have a range...
66            Rule::range => {
67                // ... let's parse it and push it onto our list of ranges
68                range_set
69                    .ranges
70                    .push(range::from_pair_iterator(record, compat)?);
71            }
72
73            // we don't need to do anything with the logical ors between ranges
74            Rule::logical_or => (),
75
76            // don't need to do anything with end-of-input
77            Rule::EOI => (),
78
79            // those are the only rules we can have, according to the grammar
80            _ => unreachable!(),
81        }
82    }
83
84    // and that's it!
85    Ok(range_set)
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    macro_rules! range_set_test {
93        ( $name:ident: $input:expr, $($x:tt)* ) => {
94                #[test]
95                fn $name() {
96                    let expected_sets = vec![$($x)*];
97
98                    let range_set: RangeSet = $input.parse().expect("parse failed");
99
100                    assert_eq!(range_set.ranges.len(), expected_sets.len());
101                    for it in range_set.ranges.iter().zip(expected_sets.iter()) {
102                        let (ai, bi ) = it;
103                        assert_eq!(ai.comparator_set.len(), *bi);
104                    }
105                }
106        };
107    }
108
109    macro_rules! range_set_nodecompat {
110        ( $name:ident: $input:expr, $($x:tt)* ) => {
111                #[test]
112                fn $name() {
113                    let expected_sets = vec![$($x)*];
114
115                    let range_set = RangeSet::parse($input, Compat::Npm).expect("parse failed");
116
117                    assert_eq!(range_set.ranges.len(), expected_sets.len());
118                    for it in range_set.ranges.iter().zip(expected_sets.iter()) {
119                        let (ai, bi ) = it;
120                        assert_eq!(ai.comparator_set.len(), *bi);
121                    }
122                }
123        };
124    }
125
126    macro_rules! should_error {
127        ( $( $name:ident: $value:expr, )* ) => {
128            $(
129                #[test]
130                fn $name() {
131                    assert!($value.parse::<RangeSet>().is_err());
132                }
133             )*
134        };
135    }
136
137    range_set_test!( one_range: "=1.2.3", 1 );
138    range_set_test!( one_range_cargo: "1.2.3", 2 ); // this parses as "^1.2.3"
139    range_set_test!( one_range_with_space: "   =1.2.3 ", 1 );
140    range_set_test!( two_ranges: ">1.2.3 || =4.5.6", 1, 1 );
141    range_set_test!( two_ranges_with_space: " >1.2.3 || =4.5.6  ", 1, 1 );
142    range_set_test!( two_ranges_with_two_comparators: ">1.2.3 <2.3.4 || >4.5.6 <5.6.7", 2, 2 );
143    range_set_test!( caret_range: "^1.2.3", 2 );
144    range_set_test!( two_empty_ranges: "||", 1, 1 );
145    range_set_test!( two_xranges: "1.2.* || 2.*", 2, 2 );
146    range_set_test!( see_issue_88: "=1.2.3+meta", 1 );
147
148    range_set_nodecompat!( node_one_range: "1.2.3", 1 ); // this parses as "=1.2.3"
149
150    should_error! {
151        err_only_gt: ">",
152        err_only_lt: "<",
153        err_only_lte: "<=",
154        err_only_gte: ">=",
155        err_only_eq: "=",
156        err_only_tilde: "~",
157        err_only_caret: "^",
158        err_leading_0_major: "01.2.3",
159        err_leading_0_minor: "1.02.3",
160        err_leading_0_patch: "1.2.03",
161        err_hyphen_with_gt: "1.2.3 - >3.4.5",
162        err_hyphen_no_2nd_version: "1.2.3 - ",
163        err_no_pre_hyphen: "~1.2.3beta",
164    }
165}