1use std::str::FromStr;
2use nom::bytes::complete::tag;
3use nom::combinator::opt;
4use nom::{AsBytes, IResult};
5use nom::branch::alt;
6use nom::sequence::tuple;
7
8use crate::Error;
9
10#[derive(Clone, Eq, PartialEq)]
11pub enum RangeSpecifier {
12 Open,
13 Inclusive(String),
14 Exclusive(String),
15}
16
17#[derive(Clone, Eq, PartialEq)]
18pub struct VersionRange {
19 pub low: RangeSpecifier,
20 pub high: RangeSpecifier
21}
22
23fn version_ident(input: &[u8]) -> IResult<&[u8], &[u8]> {
24 nom::bytes::complete::take_while1(|c| (c as char).is_ascii_alphanumeric() || c == b'-' || c == b'.' || c == b'+')(input)
25}
26
27fn whitespace(input: &[u8]) -> IResult<&[u8], &[u8]> {
28 nom::bytes::complete::take_while(|c| (c as char).is_ascii_whitespace())(input)
29}
30
31fn full_specifier(input: &[u8]) -> IResult<&[u8], VersionRange> {
32 let (remain, (_, lo_tag, _, lo, _, _, _, hi, _, hi_tag, _)) = tuple((
33 whitespace,
34 alt((tag("["), tag("("))),
35 whitespace,
36 opt(version_ident),
37 whitespace,
38 tag(b","),
39 whitespace,
40 opt(version_ident),
41 whitespace,
42 alt((tag("]"), tag(")"))),
43 whitespace
44 ))(input.as_bytes())?;
45
46 let lo = match (lo_tag, lo) {
47 (_, None) => RangeSpecifier::Open,
48 (b"[", Some(v)) => RangeSpecifier::Inclusive(String::from_utf8(v.to_owned()).unwrap()),
49 (b"(", Some(v)) => RangeSpecifier::Exclusive(String::from_utf8(v.to_owned()).unwrap()),
50 _ => unreachable!()
51 };
52
53 let hi = match (hi_tag, hi) {
54 (_, None) => RangeSpecifier::Open,
55 (b"]", Some(v)) => RangeSpecifier::Inclusive(String::from_utf8(v.to_owned()).unwrap()),
56 (b")", Some(v)) => RangeSpecifier::Exclusive(String::from_utf8(v.to_owned()).unwrap()),
57 _ => unreachable!()
58 };
59
60 Ok((remain, VersionRange {
61 low: lo,
62 high: hi
63 }))
64}
65
66fn exact_match(input: &[u8]) -> IResult<&[u8], VersionRange> {
67 let (remain, (_, _, _, version, _, _, _)) = tuple((
68 whitespace,
69 tag(b"["),
70 whitespace,
71 version_ident,
72 whitespace,
73 tag(b"]"),
74 whitespace
75 ))(input)?;
76
77 Ok((remain, VersionRange {
78 low: RangeSpecifier::Inclusive(String::from_utf8(version.to_owned()).unwrap()),
79 high: RangeSpecifier::Inclusive(String::from_utf8(version.to_owned()).unwrap())
80 }))
81}
82
83fn min_version_bare(input: &[u8]) -> IResult<&[u8], VersionRange> {
84 let (remain, version) = version_ident(input)?;
85
86 Ok((remain, VersionRange {
87 low: RangeSpecifier::Inclusive(String::from_utf8(version.to_owned()).unwrap()),
88 high: RangeSpecifier::Open
89 }))
90}
91
92impl FromStr for VersionRange {
93 type Err = Error;
94
95 fn from_str(s: &str) -> Result<Self, Self::Err> {
96 let result = alt((full_specifier, exact_match, min_version_bare))(s.as_bytes());
97
98 match result {
99 Ok((b"", v)) => Ok(v),
100 _ => Err(Error::InvalidVersionRange(s.to_owned())),
101 }
102 }
103}
104
105impl std::fmt::Debug for VersionRange {
106 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
107 write!(f, "VersionRange({})", self)
108 }
109}
110
111impl std::fmt::Display for VersionRange {
112 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
113 match &self.low {
114 RangeSpecifier::Open => write!(f, "[")?,
115 RangeSpecifier::Inclusive(v) => write!(f, "[{} ", v)?,
116 RangeSpecifier::Exclusive(v) => write!(f, "({} ", v)?,
117 }
118
119 write!(f, ", ")?;
120
121 match &self.high {
122 RangeSpecifier::Open => write!(f, "]")?,
123 RangeSpecifier::Inclusive(v) => write!(f, "{}]", v)?,
124 RangeSpecifier::Exclusive(v) => write!(f, "{})", v)?,
125 }
126
127 Ok(())
128 }
129}
130
131#[cfg(test)]
132mod test {
133 use std::str::FromStr;
134
135 use crate::model::{RangeSpecifier, VersionRange};
136
137 #[test]
138 fn test_vecs() {
139 assert_eq!("1.0".parse::<VersionRange>().unwrap(), VersionRange {
140 low: RangeSpecifier::Inclusive("1.0".to_owned()),
141 high: RangeSpecifier::Open
142 });
143
144 assert_eq!("[1.0]".parse::<VersionRange>().unwrap(), VersionRange {
145 low: RangeSpecifier::Inclusive("1.0".to_owned()),
146 high: RangeSpecifier::Inclusive("1.0".to_owned())
147 });
148
149 assert_eq!("[1.0, )".parse::<VersionRange>().unwrap(), VersionRange {
150 low: RangeSpecifier::Inclusive("1.0".to_owned()),
151 high: RangeSpecifier::Open
152 });
153 assert_eq!(" [ 1.0 ,) ".parse::<VersionRange>().unwrap(), VersionRange {
154 low: RangeSpecifier::Inclusive("1.0".to_owned()),
155 high: RangeSpecifier::Open
156 });
157 assert_eq!("(1.0, )".parse::<VersionRange>().unwrap(), VersionRange {
158 low: RangeSpecifier::Exclusive("1.0".to_owned()),
159 high: RangeSpecifier::Open
160 });
161 assert_eq!("(,1.0]".parse::<VersionRange>().unwrap(), VersionRange {
162 low: RangeSpecifier::Open,
163 high: RangeSpecifier::Inclusive("1.0".to_owned())
164 });
165 assert_eq!("(,1.0)".parse::<VersionRange>().unwrap(), VersionRange {
166 low: RangeSpecifier::Open,
167 high: RangeSpecifier::Exclusive("1.0".to_owned())
168 });
169 assert_eq!("[1.0,2.0]".parse::<VersionRange>().unwrap(), VersionRange {
170 low: RangeSpecifier::Inclusive("1.0".to_owned()),
171 high: RangeSpecifier::Inclusive("2.0".to_owned())
172 });
173 assert_eq!("(1.0,2.0)".parse::<VersionRange>().unwrap(), VersionRange {
174 low: RangeSpecifier::Exclusive("1.0".to_owned()),
175 high: RangeSpecifier::Exclusive("2.0".to_owned())
176 });
177 assert_eq!("[1.0,2.0)".parse::<VersionRange>().unwrap(), VersionRange {
178 low: RangeSpecifier::Inclusive("1.0".to_owned()),
179 high: RangeSpecifier::Exclusive("2.0".to_owned())
180 });
181 assert!(VersionRange::from_str("(1.0)").is_err());
182 }
183}