1use std::cmp::Ordering;
2
3fn epoch(version: &str) -> (&str, &str) {
4 if let Some(colon) = version.find(':') {
5 (&version[..colon], &version[colon + 1..])
6 } else {
7 ("", version)
8 }
9}
10
11fn debian_revision(version: &str) -> (&str, &str) {
12 if let Some(hyphen) = version.rfind('-') {
13 (&version[..hyphen], &version[hyphen + 1..])
14 } else {
15 (version, "")
16 }
17}
18
19fn split_point<F>(from: &str, pat: F) -> usize
20where
21 F: Fn(char) -> bool,
22{
23 for (pos, chr) in from.chars().enumerate() {
24 if !pat(chr) {
25 return pos;
26 }
27 }
28 from.len()
29}
30
31fn take_while<F>(from: &str, pat: F) -> (&str, &str)
32where
33 F: Fn(char) -> bool,
34{
35 let point = split_point(from, pat);
36 (&from[..point], &from[point..])
37}
38
39fn take_component(version: &str) -> ((&str, &str), &str) {
40 let (alpha_part, version) = take_while(version, |chr: char| !chr.is_digit(10));
41 let (num_part, version) = take_while(version, |chr: char| chr.is_digit(10));
42
43 ((alpha_part, num_part), version)
44}
45
46fn compare_digits(left: &str, right: &str) -> Ordering {
47 if left == right {
48 return Ordering::Equal;
49 }
50
51 let left = left.trim_start_matches('0');
52 let right = right.trim_start_matches('0');
53
54 match left.len().cmp(&right.len()) {
55 Ordering::Equal => left.cmp(&right),
56 other => other,
57 }
58}
59
60fn is_ascii_letter(chr: char) -> bool {
61 (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z')
62}
63
64fn compare_non_digit(left: char, right: char) -> Ordering {
65 if left == right {
66 return Ordering::Equal;
67 }
68
69 if '~' == left {
70 return Ordering::Less;
71 }
72
73 if '~' == right {
74 return Ordering::Greater;
75 }
76
77 let left_letter = is_ascii_letter(left);
78 let right_letter = is_ascii_letter(right);
79
80 if left_letter == right_letter {
81 left.cmp(&right)
82 } else if left_letter {
83 Ordering::Less
84 } else {
85 Ordering::Greater
86 }
87}
88
89fn compare_non_digits(left: &str, right: &str) -> Ordering {
90 if left == right {
91 return Ordering::Equal;
92 }
93
94 let mut left = left.chars();
95 let mut right = right.chars();
96
97 loop {
98 if let Some(l) = left.next() {
99 if let Some(r) = right.next() {
100 match compare_non_digit(l, r) {
101 Ordering::Equal => continue,
102 other => return other,
103 }
104 } else {
105 return if '~' == l {
106 Ordering::Less
107 } else {
108 Ordering::Greater
109 };
110 }
111 } else {
112 return if let Some(r) = right.next() {
113 if '~' == r {
114 Ordering::Greater
115 } else {
116 Ordering::Less
117 }
118 } else {
119 unreachable!();
121 };
122 }
123 }
124}
125
126fn compare_simple_version(mut left: &str, mut right: &str) -> Ordering {
127 if left == right {
128 return Ordering::Equal;
129 }
130
131 loop {
132 let ((left_alpha, left_digit), left_remainder) = take_component(left);
133 let ((right_alpha, right_digit), right_remainder) = take_component(right);
134
135 match compare_non_digits(left_alpha, right_alpha) {
136 Ordering::Equal => {}
137 other => return other,
138 }
139
140 match compare_digits(left_digit, right_digit) {
141 Ordering::Equal => {}
142 other => return other,
143 }
144
145 if left_remainder.is_empty() && right_remainder.is_empty() {
146 return Ordering::Equal;
147 }
148
149 left = left_remainder;
150 right = right_remainder;
151 }
152}
153
154pub fn compare_versions(left: &str, right: &str) -> Ordering {
165 if left == right {
166 return Ordering::Equal;
167 }
168
169 let (left_epoch, left) = epoch(left);
170 let (right_epoch, right) = epoch(right);
171
172 match compare_digits(left_epoch, right_epoch) {
173 Ordering::Equal => {}
174 other => return other,
175 }
176
177 let (left, left_debian) = debian_revision(left);
178 let (right, right_debian) = debian_revision(right);
179
180 match compare_simple_version(left, right) {
181 Ordering::Equal => {}
182 other => return other,
183 }
184
185 compare_simple_version(left_debian, right_debian)
186}
187
188#[cfg(test)]
189mod tests {
190 use std::cmp::Ordering;
191
192 use crate::compare_versions;
193
194 #[test]
195 fn test_epoch() {
196 use crate::epoch;
197
198 assert_eq!(("", "17"), epoch("17"));
199 assert_eq!(("1", "17"), epoch("1:17"));
200
201 assert_eq!(("1", "17:19"), epoch("1:17:19"));
203 }
204
205 #[test]
206 fn test_extract_revision() {
207 use crate::debian_revision;
208
209 assert_eq!(("17", ""), debian_revision("17"));
210 assert_eq!(("17", "2"), debian_revision("17-2"));
211 assert_eq!(("17-2", "3"), debian_revision("17-2-3"));
212 }
213
214 #[test]
215 fn test_take_component() {
216 use crate::take_component;
217
218 assert_eq!((("abc", "123"), "def456"), take_component("abc123def456"));
219 assert_eq!((("a", "123"), "def456"), take_component("a123def456"));
220 assert_eq!((("abc", "1"), "def456"), take_component("abc1def456"));
221 assert_eq!((("a", "1"), "def456"), take_component("a1def456"));
222
223 assert_eq!((("", "12"), "bc34"), take_component("12bc34"));
224 assert_eq!((("", "12"), "b34"), take_component("12b34"));
225 assert_eq!((("", "1"), "bc34"), take_component("1bc34"));
226 assert_eq!((("", "1"), "b34"), take_component("1b34"));
227
228 assert_eq!((("", "17"), ""), take_component("17"));
229 assert_eq!((("", "1"), ""), take_component("1"));
230 assert_eq!((("ab", ""), ""), take_component("ab"));
231 assert_eq!((("a", ""), ""), take_component("a"));
232
233 assert_eq!((("", ""), ""), take_component(""));
234 }
235
236 #[test]
237 fn test_compare_digits() {
238 use crate::compare_digits;
239 use std::cmp::Ordering::*;
240
241 assert_eq!(Equal, compare_digits("1", "1"));
242 assert_eq!(Equal, compare_digits("1", "01"));
243 assert_eq!(Equal, compare_digits("100", "0100"));
244 assert_eq!(Equal, compare_digits("0100", "100"));
245 assert_eq!(Equal, compare_digits("00000100", "00000000000100"));
246 assert_eq!(Equal, compare_digits("000100", "0100"));
247
248 assert_eq!(Equal, compare_digits("", "00"));
249 assert_eq!(Less, compare_digits("", "001"));
250 assert_eq!(Greater, compare_digits("001", ""));
251
252 assert_eq!(Less, compare_digits("1", "2"));
253 assert_eq!(Greater, compare_digits("2", "1"));
254
255 assert_eq!(Less, compare_digits("01", "02"));
256 assert_eq!(Greater, compare_digits("02", "01"));
257
258 assert_eq!(Less, compare_digits("10", "20"));
259 assert_eq!(Greater, compare_digits("20", "10"));
260
261 assert_eq!(Less, compare_digits("11", "12"));
262 assert_eq!(Greater, compare_digits("12", "11"));
263
264 assert_eq!(Less, compare_digits("1", "10"));
265 assert_eq!(Greater, compare_digits("10", "1"));
266 }
267
268 #[test]
269 fn test_compare_non_digits() {
270 use crate::compare_non_digits;
271 use std::cmp::Ordering::*;
272
273 assert_eq!(Equal, compare_non_digits("a", "a"));
274 assert_eq!(Equal, compare_non_digits("Z", "Z"));
275 assert_eq!(Equal, compare_non_digits("~", "~"));
276 assert_eq!(Equal, compare_non_digits("-", "-"));
277
278 assert_eq!(Less, compare_non_digits("a", "b"));
279 }
280
281 #[test]
282 fn simple() {
283 assert_eq!(Ordering::Less, compare_versions("3.0", "3.1"));
284 assert_eq!(Ordering::Greater, compare_versions("3.1", "3.0"));
285 assert_eq!(Ordering::Equal, compare_versions("3.0", "3.0"));
286 }
287}