alpmver/
version.rs

1use std::{fmt, cmp::Ordering, str::FromStr};
2
3use nom::{bytes::complete::take_while, character::complete::{alpha1, digit1}, IResult};
4
5fn take_alpha(i: &str) -> (&str, &str) {
6    let res: IResult<&str, _> = alpha1(i);
7    
8    res.unwrap_or((i, ""))
9}
10
11fn take_digits(i: &str) -> (&str, &str) {
12    let res: IResult<&str, _> = digit1(i);
13
14    res.unwrap_or((i, ""))
15}
16
17fn take_noalnum(i: &str) -> (&str, &str) {
18    let res: IResult<&str, &str> = take_while(|c: char| !c.is_ascii_alphanumeric())(i);
19
20    res.unwrap_or((i, ""))
21}
22
23const fn atend(a: &str, b: &str) -> bool {
24    a.is_empty() || b.is_empty()
25}
26
27fn vercomp(a: &str, b: &str) -> Ordering {
28    use Ordering::*;
29
30    if a == b {
31        return Equal;
32    }
33
34    let mut beg1 = a;
35    let mut beg2 = b;
36
37    let (rem1, rem2) = loop {
38        // this catches those cases where one of the strings was empty to begin with
39        if atend(beg1, beg2) {
40            break (beg1, beg2);
41        }
42
43        let (rem1, sym1) = take_noalnum(beg1);
44        let (rem2, sym2) = take_noalnum(beg2);
45
46        if atend(rem1, rem2) {
47            break (rem1, rem2);
48        }
49
50        let (sk1, sk2) = (sym1.len(), sym2.len());
51        if sk1 != sk2 {
52            return sk1.cmp(&sk2);
53        }
54
55        // next segment
56
57        let is_num = rem1.starts_with(|c: char| c.is_ascii_digit());
58        let take_fn = if is_num {
59            take_digits
60        } else {
61            take_alpha
62        };
63
64        let (rem1, chk1) = take_fn(rem1);
65        let (rem2, chk2) = take_fn(rem2);
66
67        if chk2.is_empty() {
68            // rem2 was not the same type as rem1
69            // rpm arbitrarily assumes numeric segments are
70            // greater than alphabetic ones
71
72            return match is_num {
73                true => Greater,
74                false => Less,
75            };
76        }
77
78        if is_num {
79            // convert to u128, can't fail
80            let n1 = u128::from_str(chk1).unwrap();
81            let n2 = u128::from_str(chk2).unwrap();
82            
83            let cmp = n1.cmp(&n2);
84            
85            match cmp {
86                Equal => {}, // continue
87                v => return v,
88            }
89        } else {
90            let cmp = chk1.cmp(&chk2);
91
92            match cmp {
93                Equal => {}, // continue
94                v => return v,
95            }
96        }
97
98        beg1 = rem1;
99        beg2 = rem2;
100    };
101    
102    if rem1.is_empty() && rem2.is_empty() {
103        return Equal;
104    }
105    
106    let alpha = |c: char| c.is_ascii_alphabetic();
107    
108    // strange RPM logic: 
109    // 1. if one is empty and two is !alpha, two is newer;
110    // 2. if one is alpha, two is newer;
111    // 3. otherwise, one is newer.
112    if rem1.is_empty() && !rem2.starts_with(alpha) || rem1.starts_with(alpha) {
113        Less
114    } else {
115        Greater
116    }
117}
118
119#[derive(Clone, Debug, Eq)]
120pub struct Version(String);
121
122impl Version {
123    pub fn new(s: String) -> Self {
124        Self(s)
125    }
126
127    pub fn as_components(&self) -> VersionComponents {
128        let Version(evr) = self;
129
130        // take the epoch if any
131        let (version, epoch) = take_digits(evr);
132
133        // remove any leading colon
134        let some_nocol = version.strip_prefix(':');
135
136        // find if there's an epoch
137        let (epoch, version) = if !epoch.is_empty() && some_nocol.is_some() {
138            (epoch, some_nocol.unwrap())
139        } else {
140            ("0", some_nocol.unwrap_or(evr)) 
141        };
142
143        // find the version terminator
144        let (version, release) = match version.rsplit_once('-') {
145            Some((version, release)) => (version, Some(release)),
146            None => (version, None),
147        };
148
149        VersionComponents { epoch, version, release }
150    }
151
152    pub fn as_str(&self) -> &str {
153        self.as_ref()
154    }
155
156    pub fn into_string(self) -> String {
157        self.0
158    }
159}
160
161impl AsRef<str> for Version {
162    fn as_ref(&self) -> &str {
163        &self.0
164    }
165}
166
167impl From<String> for Version {
168    fn from(s: String) -> Self {
169        Self::new(s)
170    }
171}
172
173impl <'a> From<VersionComponents<'a>> for Version {
174    fn from(vc: VersionComponents<'a>) -> Self {
175        vc.to_version()
176    }
177}
178
179impl From<&str> for Version {
180    fn from(s: &str) -> Self {
181        Self::new(s.to_owned())
182    }
183}
184
185impl Ord for Version {
186    fn cmp(&self, other: &Self) -> Ordering {
187        self.as_components().cmp(&other.as_components())
188    }
189}
190
191impl PartialEq for Version {
192    fn eq(&self, other: &Self) -> bool {
193        self.cmp(other).is_eq()
194    }
195}
196
197impl PartialOrd for Version {
198    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
199        Some(self.cmp(other))
200    }
201}
202
203impl fmt::Display for Version {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        self.0.fmt(f)
206    }
207}
208
209#[derive(Clone, Debug, Eq)]
210pub struct VersionComponents<'a> {
211    pub epoch: &'a str,
212    pub version: &'a str,
213    pub release: Option<&'a str>,
214}
215
216impl <'a> VersionComponents<'a> {
217    fn to_version(&self) -> Version {
218        let Self { epoch, version, release } = *self;
219        let release = release.unwrap_or("1");
220
221        if epoch == "0" {
222            format!("{}-{}", version, release)
223        } else {
224            format!("{}:{}-{}", epoch, version, release)
225        }.into()
226    }
227}
228
229impl <'a> Ord for VersionComponents<'a> {
230    fn cmp(&self, other: &Self) -> Ordering {
231        use Ordering::*;
232
233        let res = vercomp(self.epoch, other.epoch)
234            .then_with(|| vercomp(self.version, other.version));
235
236        match (res, self.release, other.release) {
237            (Equal, Some(rel1), Some(rel2)) => vercomp(rel1, rel2),
238            (res, ..) => res,
239        }
240    }
241}
242
243impl <'a> PartialEq for VersionComponents<'a> {
244    fn eq(&self, other: &Self) -> bool {
245        self.cmp(other).is_eq()
246    }
247}
248
249impl <'a> PartialOrd for VersionComponents<'a> {
250    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
251        Some(self.cmp(other))
252    }
253}