1 use crate::*;
2 use pest::Parser;
3 
4 use std::str::FromStr;
5 
6 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
7 pub struct RangeSet {
8     pub ranges: Vec<Range>,
9     pub compat: Compat,
10 }
11 
12 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
13 pub enum Compat {
14     Cargo, // default
15     Npm,
16 }
17 
18 impl RangeSet {
new() -> RangeSet19     fn new() -> RangeSet {
20         RangeSet {
21             ranges: Vec::new(),
22             compat: Compat::Cargo, // default
23         }
24     }
25 
parse(input: &str, compat: Compat) -> Result<Self, String>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 
39 impl FromStr for RangeSet {
40     type Err = String;
41 
from_str(input: &str) -> Result<Self, Self::Err>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
from_pair_iterator( parsed_range_set: pest::iterators::Pair<'_, Rule>, compat: Compat, ) -> Result<RangeSet, String>49 fn 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)]
89 mod 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 }
166