flexver_rs/
lib.rs

1/*
2 * To the extent possible under law, the author has dedicated all copyright
3 * and related and neighboring rights to this software to the public domain
4 * worldwide. This software is distributed without any warranty.
5 *
6 * See <http://creativecommons.org/publicdomain/zero/1.0/>
7 */
8
9use std::cmp::Ordering::{self, Equal, Greater, Less};
10use std::collections::VecDeque;
11
12#[derive(Debug)]
13enum SortingType {
14    Numerical(i64, String),
15    Lexical(String),
16    SemverPrerelease(String),
17}
18
19impl SortingType {
20    fn into_string(self) -> String {
21        match self {
22            Self::Numerical(_, a) | Self::Lexical(a) | Self::SemverPrerelease(a) => a,
23        }
24    }
25}
26
27fn split_once_rest<'a>(s: &'a str, numeric: bool) -> Option<(&str, &str)> {
28    let loc = s.find(if numeric {
29        |c: char| c.is_ascii_digit()
30    } else {
31        |c: char| !c.is_ascii_digit()
32    });
33    if let Some(index) = loc {
34        Some(s.split_at(index))
35    } else {
36        Some((s, ""))
37    }
38}
39
40fn is_semver_prerelease(s: &str) -> bool {
41    s.len() > 1 && s.starts_with('-')
42}
43
44fn decompose(str_in: &str) -> VecDeque<SortingType> {
45    if str_in.is_empty() {
46        return VecDeque::new();
47    }
48
49    let mut last_numeric = str_in.starts_with(|c: char| c.is_ascii_digit());
50    let mut s = str_in.to_owned();
51    let mut out: VecDeque<SortingType> = VecDeque::new();
52
53    if let Some((left, _)) = s.split_once('+') {
54        s = left.to_owned();
55    };
56
57    while !s.is_empty() {
58        if last_numeric {
59            if let Some((left, right)) = split_once_rest(&s, false) {
60                out.push_back(SortingType::Numerical(
61                    left.parse::<i64>().unwrap(),
62                    left.to_owned(),
63                ));
64                s = right.to_owned();
65                last_numeric = false;
66            }
67        } else if let Some((left, right)) = split_once_rest(&s, true) {
68            out.push_back(if is_semver_prerelease(left) {
69                SortingType::SemverPrerelease(left.to_string())
70            } else {
71                SortingType::Lexical(left.to_string())
72            });
73            s = right.to_owned();
74            last_numeric = true;
75        }
76    }
77
78    out
79}
80
81#[derive(Debug)]
82struct VersionComparisonIterator {
83    left: VecDeque<SortingType>,
84    right: VecDeque<SortingType>,
85}
86
87impl Iterator for VersionComparisonIterator {
88    type Item = (Option<SortingType>, Option<SortingType>);
89
90    fn next(&mut self) -> Option<Self::Item> {
91        let item = (self.left.pop_front(), self.right.pop_front());
92        if let (None, None) = item {
93            None
94        } else {
95            Some(item)
96        }
97    }
98}
99
100pub fn compare(left: &str, right: &str) -> Ordering {
101    let iter = VersionComparisonIterator {
102        left: decompose(left),
103        right: decompose(right),
104    };
105
106    for next in iter {
107        use SortingType::*;
108
109        let current = match next {
110            (Some(l), None) => {
111                if let SemverPrerelease(_) = l {
112                    Less
113                } else {
114                    Greater
115                }
116            }
117            (None, Some(r)) => {
118                if let SemverPrerelease(_) = r {
119                    Greater
120                } else {
121                    Less
122                }
123            }
124            (Some(l), Some(r)) => match (l, r) {
125                (Numerical(l, _), Numerical(r, _)) => l.cmp(&r),
126                (l, r) => l.into_string().cmp(&r.into_string()),
127            },
128            (None, None) => unreachable!(),
129        };
130
131        if current != Equal {
132            return current;
133        }
134    }
135
136    Equal
137}
138
139#[derive(Debug, Copy, Clone)]
140pub struct FlexVer<'a>(pub &'a str);
141
142impl PartialEq for FlexVer<'_> {
143    fn eq(&self, other: &Self) -> bool {
144        compare(self.0, other.0) == Equal
145    }
146}
147
148impl Eq for FlexVer<'_> {}
149
150impl PartialOrd for FlexVer<'_> {
151    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
152        Some(compare(self.0, other.0))
153    }
154}
155
156impl Ord for FlexVer<'_> {
157    fn cmp(&self, other: &Self) -> Ordering {
158        compare(self.0, other.0)
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    fn test(left: &str, right: &str, result: Ordering) {
167        assert_eq!(compare(&left, &right), result);
168        assert_eq!(
169            compare(&right, &left),
170            match result {
171                Less => Greater,
172                Greater => Less,
173                Equal => Equal,
174            }
175        );
176    }
177
178    #[test]
179    fn test_compare() {
180        test("b1.7.3", "a1.2.6", Greater);
181        test("b1.2.6", "a1.7.3", Greater);
182        test("a1.1.2", "a1.1.2_01", Less);
183        test("1.16.5-0.00.5", "1.14.2-1.3.7", Greater);
184        test("1.0.0", "1.0.0-2", Less);
185        test("1.0.0", "1.0.0_01", Less);
186        test("1.0.1", "1.0.0_01", Greater);
187        test("1.0.0_01", "1.0.1", Less);
188        test("0.17.1-beta.1", "0.17.1", Less);
189        test("0.17.1-beta.1", "0.17.1-beta.2", Less);
190        test("1.4.5_01", "1.4.5_01+fabric-1.17", Equal);
191        test("1.4.5_01", "1.4.5_01+fabric-1.17+ohno", Equal);
192        test("14w16a", "18w40b", Less);
193        test("18w40a", "18w40b", Less);
194        test("1.4.5_01+fabric-1.17", "18w40b", Less);
195        test("13w02a", "c0.3.0_01", Less);
196        test("0.6.0-1.18.x", "0.9.beta-1.18.x", Less);
197    }
198
199    #[test]
200    fn test_ord() {
201        assert!(FlexVer("b1.7.3") > FlexVer("a1.2.6"));
202        assert!(FlexVer("b1.2.6") > FlexVer("a1.7.3"));
203        assert!(FlexVer("a1.1.2") < FlexVer("a1.1.2_01"));
204        assert!(FlexVer("1.16.5-0.00.5") > FlexVer("1.14.2-1.3.7"));
205        assert!(FlexVer("1.0.0") < FlexVer("1.0.0-2"));
206        assert!(FlexVer("1.0.0") < FlexVer("1.0.0_01"));
207        assert!(FlexVer("1.0.1") > FlexVer("1.0.0_01"));
208        assert!(FlexVer("1.0.0_01") < FlexVer("1.0.1"));
209        assert!(FlexVer("0.17.1-beta.1") < FlexVer("0.17.1"));
210        assert!(FlexVer("0.17.1-beta.1") < FlexVer("0.17.1-beta.2"));
211        assert!(FlexVer("1.4.5_01") == FlexVer("1.4.5_01+fabric-1.17"));
212        assert!(FlexVer("1.4.5_01") == FlexVer("1.4.5_01+fabric-1.17+ohno"));
213        assert!(FlexVer("14w16a") < FlexVer("18w40b"));
214        assert!(FlexVer("18w40a") < FlexVer("18w40b"));
215        assert!(FlexVer("1.4.5_01+fabric-1.17") < FlexVer("18w40b"));
216        assert!(FlexVer("13w02a") < FlexVer("c0.3.0_01"));
217        assert!(FlexVer("0.6.0-1.18.x") < FlexVer("0.9.beta-1.18.x"));
218
219        assert_eq!(FlexVer("b1.7.3"), FlexVer("b1.7.3").max(FlexVer("a1.2.6")));
220        assert_eq!(FlexVer("b1.2.6"), FlexVer("b1.2.6").max(FlexVer("a1.7.3")));
221        assert_eq!(FlexVer("a1.2.6"), FlexVer("b1.7.3").min(FlexVer("a1.2.6")));
222        assert_eq!(FlexVer("a1.7.3"), FlexVer("b1.2.6").min(FlexVer("a1.7.3")));
223        assert_eq!(FlexVer("1.0.0"), FlexVer("1.0.0").max(FlexVer("1.0.0")));
224        assert_eq!(FlexVer("1.0.0"), FlexVer("1.0.0").min(FlexVer("1.0.0")));
225        assert_eq!(
226            FlexVer("1.1.0"),
227            FlexVer("1.1.0").clamp(FlexVer("1.0.0"), FlexVer("1.2.0"))
228        );
229    }
230}