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 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 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 return match is_num {
73 true => Greater,
74 false => Less,
75 };
76 }
77
78 if is_num {
79 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 => {}, v => return v,
88 }
89 } else {
90 let cmp = chk1.cmp(&chk2);
91
92 match cmp {
93 Equal => {}, 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 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 let (version, epoch) = take_digits(evr);
132
133 let some_nocol = version.strip_prefix(':');
135
136 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 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}