Skip to main content

rer_version/requirement/
requirement.rs

1use crate::requirement::parser::parse_version_range;
2use crate::version::RerVersion;
3use core::fmt;
4use lazy_static::lazy_static;
5use regex::Regex;
6use std::fmt::Debug;
7use std::hash::{Hash, Hasher};
8use version_ranges::Ranges;
9
10lazy_static! {
11    static ref SEP_REGEZ_STR: Regex =
12        Regex::new(r"[-@#]").expect("Can't compile SEP_REGEZ_STR regex");
13    static ref SEP_REGEX: Regex = Regex::new(r"[-@#=<>]").expect("Can't compile SEP_REGEX regex");
14}
15
16/// # Requirement
17///
18/// ## Description
19///
20/// A requirement is the representation of the scope of range of a specific package.
21/// Mainly it is use to represent a requierement in the list of `requieres` of a
22/// package.
23#[derive(Clone, PartialEq, Eq, Debug)]
24pub struct Requirement {
25    pub name: String,
26    pub range: Option<Ranges<RerVersion>>,
27    weak_ref: bool,
28    conflict: bool,
29    sep: char,
30    original_name: String,
31}
32
33impl Requirement {
34    /// # new
35    ///
36    /// ## Description
37    ///
38    /// Create a new requirement from a string.
39    ///
40    /// ## Arguments
41    ///
42    /// * `s` - The string to parse
43    fn parse_from_string(input_str: &str) -> Self {
44        let original_name: String = input_str.to_string();
45        let mut range = None;
46        let mut weak_ref = false;
47        let mut conflict = input_str.starts_with('!');
48        let mut sep = '-';
49
50        let mut input_str = input_str.to_string();
51        if conflict {
52            input_str.remove(0);
53        } else if input_str.starts_with('~') {
54            input_str.remove(0);
55            conflict = true;
56            weak_ref = true;
57        }
58
59        let name: String = if let Some(m) = SEP_REGEX.find(&input_str) {
60            let mut req_str = input_str[m.start()..].to_string();
61            if ['-', '@', '#'].contains(&req_str.chars().next().unwrap()) {
62                sep = req_str.remove(0);
63            }
64            if req_str.contains('|') {
65                let reqs: Vec<Ranges<RerVersion>> =
66                    req_str.split('|').map(parse_version_range).collect();
67                for req in reqs {
68                    if range.is_none() {
69                        range = Some(req);
70                    } else {
71                        range = Some(range.unwrap().union(&req));
72                    }
73                }
74            } else {
75                range = Some(parse_version_range(&req_str));
76            }
77            if conflict {
78                range = Some(range.unwrap().complement());
79            }
80            input_str[..m.start()].to_string()
81        } else if conflict {
82            input_str
83            // '~foo' equates to no effect, so range remains None
84        } else {
85            range = Some(Ranges::full());
86            input_str
87        };
88
89        Requirement {
90            name,
91            range,
92            weak_ref,
93            conflict,
94            sep,
95            original_name,
96        }
97    }
98    /// # get_pubgrub
99    ///
100    /// ## Description
101    ///
102    /// Get the name and the range of the requirement in the pubgrub format.
103    /// Use mainly to add a dependency in the pubgrub solver.
104    pub fn get_pubgrub(&self) -> (String, Ranges<RerVersion>) {
105        (self.name.clone(), self.range.clone().unwrap())
106    }
107    /// # get_name
108    ///
109    /// ## Description
110    ///
111    /// Get the name of the requirement.
112    ///
113    /// ## Example
114    ///
115    /// ```rust
116    /// use rer_version::Requirement;
117    /// let a = Requirement::from("maya-1.2.3+<2.0.0");
118    /// assert_eq!(a.get_name(), "maya");
119    /// ```
120    pub fn get_name(&self) -> &str {
121        &self.name
122    }
123    /// # get_version_range
124    ///
125    /// ## Description
126    ///
127    /// Get the range of the requirement.
128    pub fn get_version_range(&self) -> Option<Ranges<RerVersion>> {
129        self.range.clone()
130    }
131    /// # merge
132    ///
133    /// ## Description
134    ///
135    /// Merge two requirements into one. If the requirements are not compatible, return None.
136    pub fn merge(&self, other: &Self) -> Option<Self> {
137        if self.name != other.name {
138            return None; // cannot merge across object names
139        }
140
141        let merged_range = match (&self.range, &other.range) {
142            (None, _) => other.range.clone(),
143            (_, None) => self.range.clone(),
144            (Some(a), Some(b)) => {
145                if self.conflict {
146                    if other.conflict {
147                        Some(a.union(b)) // Merge conflicts by union
148                    } else {
149                        Some(Self::difference(b, a)) // Subtract self range from other
150                    }
151                } else if other.conflict {
152                    Some(Self::difference(a, b)) // Subtract other range from self
153                } else {
154                    Some(a.intersection(b)) // Merge by intersection
155                }
156            }
157        };
158
159        merged_range.map(|range| Requirement {
160            name: self.name.clone(),
161            range: Some(range),
162            weak_ref: self.weak_ref && other.weak_ref, // Assuming similar fields exist
163            conflict: self.conflict && other.conflict, // Assuming similar logic applies
164            sep: self.sep,                             // Assuming sep remains unchanged
165            original_name: self.original_name.clone(), // Assuming a typo fix in field name
166        })
167    }
168    fn difference(a: &Ranges<RerVersion>, b: &Ranges<RerVersion>) -> Ranges<RerVersion> {
169        a.intersection(&b.complement())
170    }
171    /// # is_weak_ref
172    ///
173    /// ## Description
174    ///
175    /// Check if the requirement is a weak reference.
176    pub fn is_weak_ref(&self) -> bool {
177        self.weak_ref
178    }
179    fn from_pkg_and_range(name: String, range: Ranges<RerVersion>) -> Self {
180        let original_name = format!("{}-{}", name, range);
181        Requirement {
182            name,
183            range: Some(range),
184            weak_ref: false,
185            conflict: false,
186            sep: '-',
187            original_name,
188        }
189    }
190}
191
192#[test]
193fn test_requierement() {
194    let a = Requirement::from("voodoo-1");
195    assert_eq!(a.name, "voodoo");
196    let v: RerVersion = "1".try_into().unwrap();
197    assert_eq!(a.range, Some(Ranges::between(v.clone(), v.bump())));
198    let a = Requirement::from("voodoo-1.13.0|2.1.0");
199    assert_eq!(a.name, "voodoo");
200    let v: RerVersion = "1.13.0".try_into().unwrap();
201    let v2: RerVersion = "2.1.0".try_into().unwrap();
202    assert_eq!(
203        a.range,
204        Some(Ranges::between(v.clone(), v.bump()).union(&Ranges::between(v2.clone(), v2.bump())))
205    );
206}
207
208impl fmt::Display for Requirement {
209    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210        write!(f, "{}", self.original_name)
211    }
212}
213
214impl Hash for Requirement {
215    fn hash<H: Hasher>(&self, state: &mut H) {
216        self.original_name.hash(state);
217    }
218}
219
220impl From<&str> for Requirement {
221    fn from(s: &str) -> Self {
222        Requirement::parse_from_string(s)
223    }
224}
225#[test]
226fn test_to_string() {
227    let a = Requirement::from("maya-1");
228    assert_eq!(a.to_string(), "maya-1");
229    let a = Requirement::from("maya");
230    assert_eq!(a.to_string(), "maya");
231    let a = Requirement::from("maya-1.2.3+<2.0.0");
232    assert_eq!(a.to_string(), "maya-1.2.3+<2.0.0");
233    let a = Requirement::from("maya-1.2.3+");
234    assert_eq!(a.to_string(), "maya-1.2.3+");
235}
236
237#[test]
238fn test_merge_requirement() {
239    let a = Requirement::from("foo-1.2");
240    let b = Requirement::from("~foo-1");
241    let c = a.merge(&b).unwrap();
242    assert_eq!(c.to_string(), "foo-1.2");
243    let a = Requirement::from("foo-1.2");
244    let b = Requirement::from("foo-1");
245    let c = a.merge(&b).unwrap();
246    assert_eq!(c.to_string(), "foo-1.2");
247    let a = Requirement::from("foo-1.2");
248    let b = Requirement::from("foo==1.2.2");
249    let c = a.merge(&b).unwrap();
250    let v_start: RerVersion = "1.2.2".try_into().unwrap();
251    assert_eq!(c.get_version_range(), Some(Ranges::singleton(v_start)));
252    let a = Requirement::from("foo-1.2");
253    let b = Requirement::from("~foo-1");
254    let c = a.merge(&b).unwrap();
255    let v_start: RerVersion = "1.2".try_into().unwrap();
256    let v_end: RerVersion = "1.2_".try_into().unwrap();
257    assert_eq!(c.get_version_range(), Some(Ranges::between(v_start, v_end)));
258    let a = Requirement::from("foo-1.2");
259    let b = Requirement::from("!foo-1");
260    let c = a.merge(&b).unwrap();
261    let v_start: RerVersion = "1.2".try_into().unwrap();
262    let v_end: RerVersion = "1.2_".try_into().unwrap();
263    println!("{:?}", c.get_version_range().unwrap().to_string());
264    assert_eq!(c.get_version_range(), Some(Ranges::between(v_start, v_end)));
265}
266
267/// # Requirements
268///
269/// ## Description
270///
271/// A list of requirements.
272#[derive(Clone, Debug, Default, PartialEq, Eq)]
273pub struct Requirements(Vec<Requirement>);
274impl Requirements {
275    pub fn empty() -> Self {
276        Requirements(Vec::new())
277    }
278    pub fn add(&mut self, requirement: Requirement) {
279        self.0.push(requirement);
280    }
281    /// # from
282    ///
283    /// ## Description
284    ///
285    /// Create a list of requirements from a list of string.
286    pub fn from(requirements: Vec<&str>) -> Self {
287        let requirements = requirements
288            .iter()
289            .map(|x| Requirement::parse_from_string(x))
290            .collect();
291        Requirements(requirements)
292    }
293    /// # merge
294    ///
295    /// ## Description
296    ///
297    /// Merge all the requirements into one. If the requirements are not compatible, return None.
298    pub fn merge(&self) -> Self {
299        if self.0.len() <= 1 {
300            return self.clone();
301        }
302        let mut requirements = self.0.clone();
303        let mut i = 0;
304        while i < requirements.len() {
305            let mut j = i + 1;
306            while j < requirements.len() {
307                if let Some(req) = requirements[i].merge(&requirements[j]) {
308                    requirements[i] = req;
309                    requirements.remove(j);
310                } else {
311                    j += 1;
312                }
313            }
314            i += 1;
315        }
316        Requirements(requirements)
317    }
318    pub fn extend(&mut self, other: &Self) {
319        self.0.extend(other.0.clone());
320    }
321    pub fn switch(&mut self, other: &Self) {
322        self.0.clone_from(&other.0)
323    }
324    /// # to_pubgrub
325    ///
326    /// ## Description
327    ///
328    /// Convert the requirements into a list of tuple of name and range.
329    pub fn to_pubgrub(&self) -> Vec<(String, Ranges<RerVersion>)> {
330        self.0.iter().map(|x| x.get_pubgrub()).collect()
331    }
332    /// # is_empty
333    ///
334    /// ## Description
335    ///
336    /// Check if the list of requirements is empty.
337    pub fn is_empty(&self) -> bool {
338        self.0.is_empty()
339    }
340    pub fn split_weak_ref(&self) -> (Self, Self) {
341        let mut weak_ref = Vec::new();
342        let mut strong_ref = Vec::new();
343        for req in &self.0 {
344            if req.is_weak_ref() {
345                weak_ref.push(req.clone());
346            } else {
347                strong_ref.push(req.clone());
348            }
349        }
350        (Requirements(weak_ref), Requirements(strong_ref))
351    }
352    /// # split_conflict
353    ///
354    /// ## Description
355    ///
356    /// Split the requirements into two lists. One without the conflict and one with the conflict.
357    ///
358    /// ## Returns
359    ///
360    /// A tuple of two lists of requirements. `Requirements(no_conflict), Requirements(conflict)`
361    pub fn split_conflict(&self) -> (Self, Self) {
362        let mut conflict = Vec::new();
363        let mut no_conflict = Vec::new();
364        for req in &self.0 {
365            if req.conflict {
366                conflict.push(req.clone());
367            } else {
368                no_conflict.push(req.clone());
369            }
370        }
371        (Requirements(no_conflict), Requirements(conflict))
372    }
373    pub fn reduced(
374        &mut self,
375        package_name: &String,
376        range: &Ranges<RerVersion>,
377    ) -> (Ranges<RerVersion>, Self) {
378        let new_req = Requirement::from_pkg_and_range(package_name.clone(), range.clone());
379        let requierements: Requirements = Requirements(vec![new_req]);
380        self.extend(&requierements);
381        let n = self.merge();
382        let (no_conflict, _conflict) = n.split_conflict();
383        let range = no_conflict
384            .0
385            .iter()
386            .find(|x| x.name == *package_name)
387            .unwrap()
388            .range
389            .clone()
390            .unwrap();
391        (range, n)
392    }
393}
394
395impl fmt::Display for Requirements {
396    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
397        let mut first = true;
398        for req in &self.0 {
399            if first {
400                first = false;
401            } else {
402                write!(f, ", ")?;
403            }
404            write!(f, "{}", req)?;
405        }
406        Ok(())
407    }
408}
409
410impl Iterator for Requirements {
411    type Item = Requirement;
412    fn next(&mut self) -> Option<Self::Item> {
413        self.0.pop()
414    }
415}
416
417#[test]
418fn test_merge() {
419    let requirements_str = vec!["foo-1.2", "bah-3", "~foo-1"];
420    let requirements = Requirements::from(requirements_str);
421    let requirements = requirements.merge();
422    assert_eq!(requirements.0.len(), 2);
423    assert_eq!(requirements.0[0].name, "foo");
424}
425
426#[test]
427fn test_switch() {
428    let requirements_str = vec!["foo-1.2", "bah-3", "~foo-1"];
429    let mut requirements = Requirements::from(requirements_str);
430    let requirements_str2 = vec!["foo-1.5", "bah-4", "~foo-6", "~toto-6"];
431    let requirements2 = Requirements::from(requirements_str2);
432    requirements.switch(&requirements2);
433    assert_eq!(requirements, requirements2);
434}
435
436#[test]
437fn test_reduce() {
438    let requirements_str = vec!["~foo-1.0.5"];
439    let mut requirements = Requirements::from(requirements_str);
440    let v: RerVersion = "1".try_into().unwrap();
441    let (range, _toto) =
442        requirements.reduced(&"foo".to_string(), &Ranges::between(v.clone(), v.bump()));
443    let v_req: RerVersion = "1.0.5".try_into().unwrap();
444    let req_range: Ranges<RerVersion> = Ranges::between(v_req.clone(), v_req.bump());
445    assert_eq!(range, req_range);
446}