1use crate::requirement::parser::parse_version_range;
12use crate::version::RerVersion;
13use core::fmt;
14use std::ops::Bound;
15use std::rc::Rc;
16use version_ranges::Ranges;
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub struct VersionRange(Rc<Ranges<RerVersion>>);
34
35impl VersionRange {
36 pub fn any() -> Self {
38 VersionRange(Rc::new(Ranges::full()))
39 }
40
41 pub fn empty() -> Self {
45 VersionRange(Rc::new(Ranges::empty()))
46 }
47
48 pub fn parse(s: &str) -> Self {
58 let mut range: Option<Ranges<RerVersion>> = None;
59 for part in s.split('|') {
60 let part_range = parse_version_range(part);
61 range = Some(match range {
62 None => part_range,
63 Some(acc) => acc.union(&part_range),
64 });
65 }
66 VersionRange(Rc::new(range.unwrap_or_else(Ranges::full)))
67 }
68
69 pub fn from_ranges(ranges: Ranges<RerVersion>) -> Self {
71 VersionRange(Rc::new(ranges))
72 }
73
74 pub fn as_ranges(&self) -> &Ranges<RerVersion> {
76 &self.0
77 }
78
79 pub fn into_ranges(self) -> Ranges<RerVersion> {
82 Rc::unwrap_or_clone(self.0)
83 }
84
85 pub fn from_version(version: &RerVersion) -> Self {
88 VersionRange(Rc::new(Ranges::between(version.clone(), version.bump())))
89 }
90
91 pub fn from_versions<I: IntoIterator<Item = RerVersion>>(versions: I) -> Self {
98 VersionRange(Rc::new(
99 versions
100 .into_iter()
101 .map(|v| (Bound::Included(v.clone()), Bound::Included(v)))
102 .collect(),
103 ))
104 }
105
106 pub fn is_any(&self) -> bool {
108 *self.0 == Ranges::full()
109 }
110
111 pub fn is_empty(&self) -> bool {
113 self.0.is_empty()
114 }
115
116 pub fn intersection(&self, other: &Self) -> Option<Self> {
118 let result = self.0.intersection(&other.0);
119 if result.is_empty() {
120 None
121 } else {
122 Some(VersionRange(Rc::new(result)))
123 }
124 }
125
126 pub fn union(&self, other: &Self) -> Self {
128 VersionRange(Rc::new(self.0.union(&other.0)))
129 }
130
131 pub fn inverse(&self) -> Option<Self> {
134 if self.is_any() {
135 None
136 } else {
137 Some(VersionRange(Rc::new(self.0.complement())))
138 }
139 }
140
141 pub fn difference(&self, other: &Self) -> Option<Self> {
144 match other.inverse() {
145 None => None,
146 Some(inverse) => self.intersection(&inverse),
147 }
148 }
149
150 pub fn issuperset(&self, other: &Self) -> bool {
152 other.0.subset_of(&self.0)
153 }
154
155 pub fn issubset(&self, other: &Self) -> bool {
157 other.issuperset(self)
158 }
159
160 pub fn intersects(&self, other: &Self) -> bool {
162 !self.0.intersection(&other.0).is_empty()
163 }
164
165 pub fn contains(&self, version: &RerVersion) -> bool {
167 self.0.contains(version)
168 }
169
170 pub fn to_versions(&self) -> Option<Vec<RerVersion>> {
174 let mut versions = Vec::new();
175 for (lower, upper) in self.0.iter() {
176 if let (Bound::Included(low), Bound::Included(high)) = (lower, upper) {
177 if low == high {
178 versions.push(low.clone());
179 }
180 }
181 }
182 if versions.is_empty() {
183 None
184 } else {
185 Some(versions)
186 }
187 }
188
189 pub fn span(&self) -> Self {
192 match self.0.bounding_range() {
193 None => VersionRange::empty(),
194 Some((lower, upper)) => VersionRange(Rc::new(Ranges::from_range_bounds((
195 clone_bound(lower),
196 clone_bound(upper),
197 )))),
198 }
199 }
200
201 pub fn split(&self) -> Vec<Self> {
204 self.0
205 .iter()
206 .map(|(lower, upper)| {
207 VersionRange(Rc::new(Ranges::from_range_bounds((
208 lower.clone(),
209 upper.clone(),
210 ))))
211 })
212 .collect()
213 }
214}
215
216fn clone_bound(bound: Bound<&RerVersion>) -> Bound<RerVersion> {
218 match bound {
219 Bound::Included(v) => Bound::Included(v.clone()),
220 Bound::Excluded(v) => Bound::Excluded(v.clone()),
221 Bound::Unbounded => Bound::Unbounded,
222 }
223}
224
225fn format_segment(lower: &Bound<RerVersion>, upper: &Bound<RerVersion>) -> String {
228 if matches!(upper, Bound::Unbounded) {
230 return match lower {
231 Bound::Unbounded => String::new(),
232 Bound::Included(v) => format!("{v}+"),
233 Bound::Excluded(v) => format!(">{v}"),
234 };
235 }
236 let (upper_version, upper_inclusive) = match upper {
237 Bound::Included(v) => (v, true),
238 Bound::Excluded(v) => (v, false),
239 Bound::Unbounded => unreachable!(),
240 };
241 let lower_version = match lower {
242 Bound::Included(v) | Bound::Excluded(v) => Some(v),
243 Bound::Unbounded => None,
244 };
245 let lower_inclusive = !matches!(lower, Bound::Excluded(_));
246
247 if lower_version == Some(upper_version) {
249 return format!("=={upper_version}");
250 }
251 if lower_inclusive && upper_inclusive {
253 return match lower_version {
254 Some(lv) => format!("{lv}..{upper_version}"),
255 None => format!("<={upper_version}"),
256 };
257 }
258 if lower_inclusive && !upper_inclusive {
260 if let Some(lv) = lower_version {
261 if &lv.bump() == upper_version {
262 return lv.to_string();
263 }
264 }
265 }
266 let lower_str = match lower {
268 Bound::Unbounded => String::new(),
269 Bound::Included(v) => format!("{v}+"),
270 Bound::Excluded(v) => format!(">{v}"),
271 };
272 let upper_str = if upper_inclusive {
273 format!("<={upper_version}")
274 } else {
275 format!("<{upper_version}")
276 };
277 format!("{lower_str}{upper_str}")
278}
279
280impl fmt::Display for VersionRange {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 let mut first = true;
284 for (lower, upper) in self.0.iter() {
285 if !first {
286 write!(f, "|")?;
287 }
288 first = false;
289 write!(f, "{}", format_segment(lower, upper))?;
290 }
291 Ok(())
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 fn v(s: &str) -> RerVersion {
300 RerVersion::try_from(s).unwrap()
301 }
302
303 #[test]
304 fn test_any_and_empty() {
305 assert!(VersionRange::any().is_any());
306 assert!(!VersionRange::any().is_empty());
307 assert!(VersionRange::empty().is_empty());
308 assert!(!VersionRange::empty().is_any());
309 assert!(VersionRange::parse("").is_any());
311 }
312
313 #[test]
314 fn test_intersection_none_when_disjoint() {
315 let a = VersionRange::parse("1+<2");
316 let b = VersionRange::parse("3+<4");
317 assert!(a.intersection(&b).is_none());
318 let c = VersionRange::parse("1.5+<3");
319 let hit = a.intersection(&c).unwrap();
320 assert!(hit.contains(&v("1.6")));
321 assert!(!hit.contains(&v("2.0")));
322 }
323
324 #[test]
325 fn test_inverse_none_only_for_any() {
326 assert!(VersionRange::any().inverse().is_none());
327 let inv = VersionRange::parse("2+<3").inverse().unwrap();
328 assert!(inv.contains(&v("1.0")));
329 assert!(inv.contains(&v("4.0")));
330 assert!(!inv.contains(&v("2.5")));
331 }
332
333 #[test]
334 fn test_difference() {
335 let a = VersionRange::parse("1+<5");
337 let b = VersionRange::parse("2+<3");
338 let diff = a.difference(&b).unwrap();
339 assert!(diff.contains(&v("1.5")));
340 assert!(!diff.contains(&v("2.5")));
341 assert!(diff.contains(&v("3.0")));
342 assert!(a.difference(&VersionRange::any()).is_none());
344 assert!(b.difference(&a).is_none());
346 }
347
348 #[test]
349 fn test_superset_subset_intersects() {
350 let wide = VersionRange::parse("1+<10");
351 let narrow = VersionRange::parse("2+<3");
352 assert!(wide.issuperset(&narrow));
353 assert!(!narrow.issuperset(&wide));
354 assert!(narrow.issubset(&wide));
355 assert!(wide.intersects(&narrow));
356 assert!(!narrow.intersects(&VersionRange::parse("5+<6")));
357 }
358
359 #[test]
360 fn test_from_version_superset() {
361 let r = VersionRange::from_version(&v("3"));
363 assert!(r.contains(&v("3")));
364 assert!(r.contains(&v("3.0")));
365 assert!(r.contains(&v("3.1.4")));
366 assert!(!r.contains(&v("4")));
367 }
368
369 #[test]
370 fn test_from_versions_and_to_versions() {
371 let r = VersionRange::from_versions([v("3"), v("5.1"), v("4")]);
372 let mut versions = r.to_versions().unwrap();
373 versions.sort();
374 assert_eq!(versions, vec![v("3"), v("4"), v("5.1")]);
375 assert!(VersionRange::parse("1+<2").to_versions().is_none());
377 }
378
379 #[test]
380 fn test_display_rez_format() {
381 assert_eq!(VersionRange::parse("4").to_string(), "4");
383 assert_eq!(VersionRange::parse("1.0").to_string(), "1.0");
384 assert_eq!(VersionRange::parse("3+<5").to_string(), "3+<5");
385 assert_eq!(VersionRange::parse("3+").to_string(), "3+");
386 assert_eq!(VersionRange::parse("<3").to_string(), "<3");
387 assert_eq!(VersionRange::parse("==1.0.1").to_string(), "==1.0.1");
388 assert_eq!(VersionRange::parse("2|5").to_string(), "2|5");
389 assert_eq!(VersionRange::any().to_string(), "");
390 }
391
392 #[test]
393 fn test_span_and_split() {
394 let r = VersionRange::parse("2+<4|6+<8");
395 let span = r.span();
396 assert!(span.contains(&v("5")));
397 assert!(span.contains(&v("3")));
398 assert!(!span.contains(&v("8")));
399 let parts = r.split();
400 assert_eq!(parts.len(), 2);
401 assert!(parts[0].contains(&v("3")));
402 assert!(!parts[0].contains(&v("6")));
403 assert!(parts[1].contains(&v("6")));
404 }
405}