rez_next_version/range/
mod.rs1mod parser;
4mod satisfiability;
5mod types;
6
7#[cfg(test)]
8#[path = "tests.rs"]
9mod tests;
10
11use super::Version;
12use rez_next_common::RezCoreError;
13use serde::{Deserialize, Serialize};
14use types::BoundSet;
15
16use parser::parse_range_str;
17use satisfiability::{bound_sets_intersect, is_bound_set_satisfiable};
18use types::Bound;
19
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22pub struct VersionRange {
23 pub range_str: String,
25 #[serde(skip)]
27 bound_sets: Vec<BoundSet>,
28 #[serde(skip)]
30 is_parsed: bool,
31 #[serde(skip)]
33 subtract_from: Vec<VersionRange>,
34}
35
36impl VersionRange {
37 #[allow(clippy::missing_errors_doc)]
39 pub fn new(range_str: &str) -> Result<Self, RezCoreError> {
40 Self::parse(range_str)
41 }
42
43 pub fn any() -> Self {
45 VersionRange {
46 range_str: String::new(),
47 bound_sets: vec![BoundSet::any()],
48 is_parsed: true,
49 subtract_from: Vec::new(),
50 }
51 }
52
53 pub fn none() -> Self {
55 VersionRange {
56 range_str: "!*".to_string(),
57 bound_sets: vec![BoundSet::none()],
58 is_parsed: true,
59 subtract_from: Vec::new(),
60 }
61 }
62
63 pub fn parse(range_str: &str) -> Result<Self, RezCoreError> {
75 let trimmed = range_str.trim();
76 let bound_sets = parse_range_str(trimmed)?;
77 Ok(VersionRange {
78 range_str: range_str.to_string(),
79 bound_sets,
80 is_parsed: true,
81 subtract_from: Vec::new(),
82 })
83 }
84
85 pub fn contains(&self, version: &Version) -> bool {
87 if !self.is_parsed || self.bound_sets.is_empty() {
88 return true;
89 }
90 let in_self = self.bound_sets.iter().any(|bs| bs.contains(version));
92 if !in_self {
93 return false;
94 }
95 for sub_range in &self.subtract_from {
97 if sub_range.contains(version) {
98 return false;
99 }
100 }
101 true
102 }
103
104 pub fn as_str(&self) -> &str {
106 &self.range_str
107 }
108
109 pub fn intersects(&self, other: &VersionRange) -> bool {
111 if self.is_any() || other.is_any() {
113 return true;
114 }
115 for bs_self in &self.bound_sets {
118 for bs_other in &other.bound_sets {
119 if bound_sets_intersect(bs_self, bs_other) {
121 return true;
122 }
123 }
124 }
125 false
126 }
127
128 pub fn intersect(&self, other: &VersionRange) -> Option<VersionRange> {
130 if self.is_any() {
131 return Some(other.clone());
132 }
133 if other.is_any() {
134 return Some(self.clone());
135 }
136 let mut result_sets = Vec::new();
139 for bs_self in &self.bound_sets {
140 for bs_other in &other.bound_sets {
141 let mut merged = bs_self.bounds.clone();
142 merged.extend(bs_other.bounds.clone());
143 let merged_set = BoundSet { bounds: merged };
144 if is_bound_set_satisfiable(&merged_set) {
146 result_sets.push(merged_set);
147 }
148 }
149 }
150
151 if result_sets.is_empty() {
152 return None;
153 }
154 let new_str = format!("({})&({})", self.range_str, other.range_str);
155 let mut combined_subtracts = self.subtract_from.clone();
156 combined_subtracts.extend(other.subtract_from.clone());
157 Some(VersionRange {
158 range_str: new_str,
159 bound_sets: result_sets,
160 is_parsed: true,
161 subtract_from: combined_subtracts,
162 })
163 }
164
165 pub fn union(&self, other: &VersionRange) -> VersionRange {
167 let new_str = format!("{}|{}", self.range_str, other.range_str);
168 let mut sets = self.bound_sets.clone();
169 sets.extend(other.bound_sets.clone());
170 VersionRange {
171 range_str: new_str,
172 bound_sets: sets,
173 is_parsed: true,
174 subtract_from: Vec::new(),
175 }
176 }
177
178 pub fn subtract(&self, other: &VersionRange) -> Option<VersionRange> {
181 if other.is_any() {
182 return None; }
184 if other.is_empty() {
185 return Some(self.clone());
186 }
187 if self.is_empty() {
188 return None;
189 }
190 let new_str = format!("({})-({})", self.range_str, other.range_str);
192 let mut subtracts = self.subtract_from.clone();
193 subtracts.push(other.clone());
194 let range = VersionRange {
195 range_str: new_str,
196 bound_sets: self.bound_sets.clone(),
197 is_parsed: true,
198 subtract_from: subtracts,
199 };
200 let probes = self.collect_probe_versions_with_other(other);
202 let has_any = probes.iter().any(|v| range.contains(v));
203 if has_any { Some(range) } else { None }
204 }
205
206 pub fn is_any(&self) -> bool {
208 let s = self.range_str.trim();
209 if s.is_empty() || s == "*" {
210 return true;
211 }
212 self.bound_sets
214 .iter()
215 .all(|bs| bs.bounds.is_empty() || bs.bounds.iter().all(|b| matches!(b, Bound::Any)))
216 }
217
218 pub fn is_subset_of(&self, other: &VersionRange) -> bool {
221 if other.is_any() {
222 return true;
223 }
224 if self.is_any() {
225 return other.is_any();
226 }
227 if self.is_empty() {
228 return true;
229 }
230 let probe_versions = self.collect_probe_versions_with_other(other);
231 for v in &probe_versions {
232 if self.contains(v) && !other.contains(v) {
233 return false;
234 }
235 }
236 true
237 }
238
239 pub fn is_superset_of(&self, other: &VersionRange) -> bool {
241 other.is_subset_of(self)
242 }
243
244 fn collect_probe_versions_with_other(&self, other: &VersionRange) -> Vec<Version> {
246 let mut versions = Vec::new();
247 for range in [self as &VersionRange, other] {
248 for bs in &range.bound_sets {
249 for bound in &bs.bounds {
250 match bound {
251 Bound::Ge(v)
252 | Bound::Gt(v)
253 | Bound::Le(v)
254 | Bound::Lt(v)
255 | Bound::Eq(v)
256 | Bound::Ne(v)
257 | Bound::Compatible(v) => {
258 versions.push(v.clone());
259 if let Ok(bumped) = Version::parse(&format!("{}.999999", v.as_str())) {
260 versions.push(bumped);
261 }
262 }
263 _ => {}
264 }
265 }
266 }
267 }
268 for s in &["0.0.1", "999.999.999"] {
269 if let Ok(v) = Version::parse(s) {
270 versions.push(v);
271 }
272 }
273 versions
274 }
275
276 pub fn is_empty(&self) -> bool {
278 let s = self.range_str.trim();
279 if s == "empty" || s == "!*" {
280 return true;
281 }
282 if self.is_parsed && !self.bound_sets.is_empty() {
283 return self
284 .bound_sets
285 .iter()
286 .all(|bs| bs.bounds.iter().any(|b| matches!(b, Bound::None)));
287 }
288 false
289 }
290}