1use crate::requirement::parser::parse_version_range;
12use crate::version::RerVersion;
13use core::fmt;
14use std::ops::Bound;
15use version_ranges::Ranges;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct VersionRange(Ranges<RerVersion>);
24
25impl VersionRange {
26 pub fn any() -> Self {
28 VersionRange(Ranges::full())
29 }
30
31 pub fn empty() -> Self {
35 VersionRange(Ranges::empty())
36 }
37
38 pub fn parse(s: &str) -> Self {
48 let mut range: Option<Ranges<RerVersion>> = None;
49 for part in s.split('|') {
50 let part_range = parse_version_range(part);
51 range = Some(match range {
52 None => part_range,
53 Some(acc) => acc.union(&part_range),
54 });
55 }
56 VersionRange(range.unwrap_or_else(Ranges::full))
57 }
58
59 pub fn from_ranges(ranges: Ranges<RerVersion>) -> Self {
61 VersionRange(ranges)
62 }
63
64 pub fn as_ranges(&self) -> &Ranges<RerVersion> {
66 &self.0
67 }
68
69 pub fn into_ranges(self) -> Ranges<RerVersion> {
71 self.0
72 }
73
74 pub fn from_version(version: &RerVersion) -> Self {
77 VersionRange(Ranges::between(version.clone(), version.bump()))
78 }
79
80 pub fn from_versions<I: IntoIterator<Item = RerVersion>>(versions: I) -> Self {
87 VersionRange(
88 versions
89 .into_iter()
90 .map(|v| (Bound::Included(v.clone()), Bound::Included(v)))
91 .collect(),
92 )
93 }
94
95 pub fn is_any(&self) -> bool {
97 self.0 == Ranges::full()
98 }
99
100 pub fn is_empty(&self) -> bool {
102 self.0.is_empty()
103 }
104
105 pub fn intersection(&self, other: &Self) -> Option<Self> {
107 let result = self.0.intersection(&other.0);
108 if result.is_empty() {
109 None
110 } else {
111 Some(VersionRange(result))
112 }
113 }
114
115 pub fn union(&self, other: &Self) -> Self {
117 VersionRange(self.0.union(&other.0))
118 }
119
120 pub fn inverse(&self) -> Option<Self> {
123 if self.is_any() {
124 None
125 } else {
126 Some(VersionRange(self.0.complement()))
127 }
128 }
129
130 pub fn difference(&self, other: &Self) -> Option<Self> {
133 match other.inverse() {
134 None => None,
135 Some(inverse) => self.intersection(&inverse),
136 }
137 }
138
139 pub fn issuperset(&self, other: &Self) -> bool {
141 other.0.subset_of(&self.0)
142 }
143
144 pub fn issubset(&self, other: &Self) -> bool {
146 other.issuperset(self)
147 }
148
149 pub fn intersects(&self, other: &Self) -> bool {
151 !self.0.intersection(&other.0).is_empty()
152 }
153
154 pub fn contains(&self, version: &RerVersion) -> bool {
156 self.0.contains(version)
157 }
158
159 pub fn to_versions(&self) -> Option<Vec<RerVersion>> {
163 let mut versions = Vec::new();
164 for (lower, upper) in self.0.iter() {
165 if let (Bound::Included(low), Bound::Included(high)) = (lower, upper) {
166 if low == high {
167 versions.push(low.clone());
168 }
169 }
170 }
171 if versions.is_empty() {
172 None
173 } else {
174 Some(versions)
175 }
176 }
177
178 pub fn span(&self) -> Self {
181 match self.0.bounding_range() {
182 None => VersionRange::empty(),
183 Some((lower, upper)) => VersionRange(Ranges::from_range_bounds((
184 clone_bound(lower),
185 clone_bound(upper),
186 ))),
187 }
188 }
189
190 pub fn split(&self) -> Vec<Self> {
193 self.0
194 .iter()
195 .map(|(lower, upper)| {
196 VersionRange(Ranges::from_range_bounds((lower.clone(), upper.clone())))
197 })
198 .collect()
199 }
200}
201
202fn clone_bound(bound: Bound<&RerVersion>) -> Bound<RerVersion> {
204 match bound {
205 Bound::Included(v) => Bound::Included(v.clone()),
206 Bound::Excluded(v) => Bound::Excluded(v.clone()),
207 Bound::Unbounded => Bound::Unbounded,
208 }
209}
210
211fn format_segment(lower: &Bound<RerVersion>, upper: &Bound<RerVersion>) -> String {
214 if matches!(upper, Bound::Unbounded) {
216 return match lower {
217 Bound::Unbounded => String::new(),
218 Bound::Included(v) => format!("{v}+"),
219 Bound::Excluded(v) => format!(">{v}"),
220 };
221 }
222 let (upper_version, upper_inclusive) = match upper {
223 Bound::Included(v) => (v, true),
224 Bound::Excluded(v) => (v, false),
225 Bound::Unbounded => unreachable!(),
226 };
227 let lower_version = match lower {
228 Bound::Included(v) | Bound::Excluded(v) => Some(v),
229 Bound::Unbounded => None,
230 };
231 let lower_inclusive = !matches!(lower, Bound::Excluded(_));
232
233 if lower_version == Some(upper_version) {
235 return format!("=={upper_version}");
236 }
237 if lower_inclusive && upper_inclusive {
239 return match lower_version {
240 Some(lv) => format!("{lv}..{upper_version}"),
241 None => format!("<={upper_version}"),
242 };
243 }
244 if lower_inclusive && !upper_inclusive {
246 if let Some(lv) = lower_version {
247 if &lv.bump() == upper_version {
248 return lv.to_string();
249 }
250 }
251 }
252 let lower_str = match lower {
254 Bound::Unbounded => String::new(),
255 Bound::Included(v) => format!("{v}+"),
256 Bound::Excluded(v) => format!(">{v}"),
257 };
258 let upper_str = if upper_inclusive {
259 format!("<={upper_version}")
260 } else {
261 format!("<{upper_version}")
262 };
263 format!("{lower_str}{upper_str}")
264}
265
266impl fmt::Display for VersionRange {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269 let mut first = true;
270 for (lower, upper) in self.0.iter() {
271 if !first {
272 write!(f, "|")?;
273 }
274 first = false;
275 write!(f, "{}", format_segment(lower, upper))?;
276 }
277 Ok(())
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 fn v(s: &str) -> RerVersion {
286 RerVersion::try_from(s).unwrap()
287 }
288
289 #[test]
290 fn test_any_and_empty() {
291 assert!(VersionRange::any().is_any());
292 assert!(!VersionRange::any().is_empty());
293 assert!(VersionRange::empty().is_empty());
294 assert!(!VersionRange::empty().is_any());
295 assert!(VersionRange::parse("").is_any());
297 }
298
299 #[test]
300 fn test_intersection_none_when_disjoint() {
301 let a = VersionRange::parse("1+<2");
302 let b = VersionRange::parse("3+<4");
303 assert!(a.intersection(&b).is_none());
304 let c = VersionRange::parse("1.5+<3");
305 let hit = a.intersection(&c).unwrap();
306 assert!(hit.contains(&v("1.6")));
307 assert!(!hit.contains(&v("2.0")));
308 }
309
310 #[test]
311 fn test_inverse_none_only_for_any() {
312 assert!(VersionRange::any().inverse().is_none());
313 let inv = VersionRange::parse("2+<3").inverse().unwrap();
314 assert!(inv.contains(&v("1.0")));
315 assert!(inv.contains(&v("4.0")));
316 assert!(!inv.contains(&v("2.5")));
317 }
318
319 #[test]
320 fn test_difference() {
321 let a = VersionRange::parse("1+<5");
323 let b = VersionRange::parse("2+<3");
324 let diff = a.difference(&b).unwrap();
325 assert!(diff.contains(&v("1.5")));
326 assert!(!diff.contains(&v("2.5")));
327 assert!(diff.contains(&v("3.0")));
328 assert!(a.difference(&VersionRange::any()).is_none());
330 assert!(b.difference(&a).is_none());
332 }
333
334 #[test]
335 fn test_superset_subset_intersects() {
336 let wide = VersionRange::parse("1+<10");
337 let narrow = VersionRange::parse("2+<3");
338 assert!(wide.issuperset(&narrow));
339 assert!(!narrow.issuperset(&wide));
340 assert!(narrow.issubset(&wide));
341 assert!(wide.intersects(&narrow));
342 assert!(!narrow.intersects(&VersionRange::parse("5+<6")));
343 }
344
345 #[test]
346 fn test_from_version_superset() {
347 let r = VersionRange::from_version(&v("3"));
349 assert!(r.contains(&v("3")));
350 assert!(r.contains(&v("3.0")));
351 assert!(r.contains(&v("3.1.4")));
352 assert!(!r.contains(&v("4")));
353 }
354
355 #[test]
356 fn test_from_versions_and_to_versions() {
357 let r = VersionRange::from_versions([v("3"), v("5.1"), v("4")]);
358 let mut versions = r.to_versions().unwrap();
359 versions.sort();
360 assert_eq!(versions, vec![v("3"), v("4"), v("5.1")]);
361 assert!(VersionRange::parse("1+<2").to_versions().is_none());
363 }
364
365 #[test]
366 fn test_display_rez_format() {
367 assert_eq!(VersionRange::parse("4").to_string(), "4");
369 assert_eq!(VersionRange::parse("1.0").to_string(), "1.0");
370 assert_eq!(VersionRange::parse("3+<5").to_string(), "3+<5");
371 assert_eq!(VersionRange::parse("3+").to_string(), "3+");
372 assert_eq!(VersionRange::parse("<3").to_string(), "<3");
373 assert_eq!(VersionRange::parse("==1.0.1").to_string(), "==1.0.1");
374 assert_eq!(VersionRange::parse("2|5").to_string(), "2|5");
375 assert_eq!(VersionRange::any().to_string(), "");
376 }
377
378 #[test]
379 fn test_span_and_split() {
380 let r = VersionRange::parse("2+<4|6+<8");
381 let span = r.span();
382 assert!(span.contains(&v("5")));
383 assert!(span.contains(&v("3")));
384 assert!(!span.contains(&v("8")));
385 let parts = r.split();
386 assert_eq!(parts.len(), 2);
387 assert!(parts[0].contains(&v("3")));
388 assert!(!parts[0].contains(&v("6")));
389 assert!(parts[1].contains(&v("6")));
390 }
391}