1use std::cmp::Ordering;
4
5pub const CHAR_ORDER: &'static [u8] = &[
6 255u8, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
7 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
8 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 27,
9 255, 28, 255, 255, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 255, 255, 255,
10 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
11 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
12 255, 255, 255, 255, 255, 255, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
13 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 255, 255, 255, 0, 255,
14 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
15 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
16 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
17 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
18 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
19 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
20 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
21 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
22 255, 255, 255, 255, 255, 255, 255, 255,
23];
24
25fn compare_alpha(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
37 for (&ca, &cb) in a.iter().zip(b.iter()) {
39 let pa = CHAR_ORDER[usize::from(ca)];
40 let pb = CHAR_ORDER[usize::from(cb)];
41 match pa.cmp(&pb) {
42 Ordering::Equal => {},
43 o => return o,
44 }
45 }
46
47 if a.len() == b.len() {
49 Ordering::Equal
50 } else if a.len() < b.len() {
52 if b[a.len()] == b'~' {
53 Ordering::Greater
54 } else {
55 Ordering::Less
56 }
57 } else if a.len() > b.len() {
58 if a[b.len()] == b'~' {
59 Ordering::Less
60 } else {
61 Ordering::Greater
62 }
63 } else {
64 unreachable!()
65 }
66}
67
68fn position<T, F>(slice: &[T], start: usize, pred: F) -> usize
69where F: FnMut(&T) -> bool {
70 slice[start ..].iter().position(pred).map(|idx| idx + start).unwrap_or(slice.len())
71}
72
73fn parse_int(slice: &[u8]) -> u32 {
74 use std::str::from_utf8;
75 from_utf8(slice).ok().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0)
76}
77
78pub fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering {
105 let a = a.as_bytes();
106 let b = b.as_bytes();
107
108 let mut pos_a = 0;
111 let mut pos_b = 0;
112 while pos_a < a.len() || pos_b < b.len() {
113 {
115 let end_alpha_a = position(a, pos_a, u8::is_ascii_digit);
116 let end_alpha_b = position(b, pos_b, u8::is_ascii_digit);
117 match compare_alpha(&a[pos_a .. end_alpha_a], &b[pos_b .. end_alpha_b]) {
118 Ordering::Equal => {},
119 o => return o,
120 }
121 pos_a = end_alpha_a;
122 pos_b = end_alpha_b;
123 }
124
125 {
127 let end_num_a = position(a, pos_a, |c| !c.is_ascii_digit());
128 let end_num_b = position(b, pos_b, |c| !c.is_ascii_digit());
129 let num_a = parse_int(&a[pos_a .. end_num_a]);
130 let num_b = parse_int(&b[pos_b .. end_num_b]);
131 match num_a.cmp(&num_b) {
132 Ordering::Equal => {},
133 o => return o,
134 }
135 pos_a = end_num_a;
136 pos_b = end_num_b;
137 }
138 }
139 Ordering::Equal
140}
141
142#[cfg(test)]
143mod tests {
144 use std::cmp::Ordering;
145
146 use crate::Version;
147 use super::{CHAR_ORDER, compare_alpha};
148
149 struct PrioSetter {
150 prio: u8,
151 char_order: [u8; 256],
152 }
153
154 impl PrioSetter {
155 fn new() -> PrioSetter {
156 PrioSetter {
157 prio: 0,
158 char_order: [255u8; 256],
159 }
160 }
161
162 fn set(&mut self, character: u8) {
163 self.char_order[usize::from(character)] = self.prio;
164 self.prio += 1;
165 }
166
167 fn build(self) -> [u8; 256] {
168 self.char_order
169 }
170 }
171
172 #[test]
173 fn test_char_order() {
174 let mut prios = PrioSetter::new();
176 prios.set(b'~');
177 for c in b'a' ..= b'z' {
178 prios.set(c);
179 }
180 prios.set(b'+');
181 prios.set(b'-');
182 for c in b'0' ..= b'9' {
183 prios.set(c);
184 }
185 let prios = prios.build();
186
187 print!("[");
188 for c in &prios as &[u8] {
189 print!("{}, ", c);
190 }
191 println!("]");
192
193 assert!(prios.len() == CHAR_ORDER.len());
194 assert!(prios.iter().zip(CHAR_ORDER.iter()).all(|(a, b)| a == b));
195 }
196
197 #[test]
198 fn test_compare_alpha() {
199 assert_eq!(compare_alpha(b"test", b"test"), Ordering::Equal);
201 assert_eq!(compare_alpha(b"", b""), Ordering::Equal);
202 assert_eq!(compare_alpha(b"t1", b"t2"), Ordering::Less);
204 assert_eq!(compare_alpha(b"t2", b"t1"), Ordering::Greater);
205 assert_eq!(compare_alpha(b"t133", b"t2"), Ordering::Less);
206 assert_eq!(compare_alpha(b"t2", b"t133"), Ordering::Greater);
207 assert_eq!(compare_alpha(b"ta", b"tb"), Ordering::Less);
208 assert_eq!(compare_alpha(b"tb", b"ta"), Ordering::Greater);
209 assert_eq!(compare_alpha(b"tz", b"test"), Ordering::Greater);
210 assert_eq!(compare_alpha(b"test", b"tz"), Ordering::Less);
211 assert_eq!(compare_alpha(b"test", b"te5t"), Ordering::Less);
213 assert_eq!(compare_alpha(b"te5t", b"test"), Ordering::Greater);
214 assert_eq!(compare_alpha(b"test", b"te"), Ordering::Greater);
216 assert_eq!(compare_alpha(b"te", b"test"), Ordering::Less);
217 assert_eq!(compare_alpha(b"te-", b"te"), Ordering::Greater);
218 assert_eq!(compare_alpha(b"te", b"te-"), Ordering::Less);
219 assert_eq!(compare_alpha(b"te~", b"te"), Ordering::Less);
221 assert_eq!(compare_alpha(b"te", b"te~"), Ordering::Greater);
222 }
223
224 #[test]
225 fn test_compare_versions() {
226 assert!(Version("".into()) == Version("".into()));
227 assert!(Version("1.2".into()) == Version("1.2".into()));
228 assert!(Version("1.2".into()) < Version("1.2.0".into()));
229 assert!(Version("1.3.1".into()) > Version("1.1.3".into()));
230 assert!(Version("1.1.3".into()) < Version("1.3.1".into()));
231 assert!(Version("1.1~rc1".into()) < Version("1.1".into()));
232 assert!(Version("1.1-fix1".into()) > Version("1.1".into()));
233 }
234}