1use core::cmp::Ordering;
2use lazy_static::lazy_static;
3use rand::{distributions::Alphanumeric, Rng};
4use regex::Regex;
5use std::fmt;
6use std::rc::Rc;
7
8lazy_static! {
9 static ref ALPHABET_REGEX: Regex =
10 Regex::new(r"[a-zA-Z0-9_]+").expect("Can't compile ALPHABET_REGEX regex");
11 static ref SEMVER_REGEX: Regex = Regex::new(
12 r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
13 ).expect("Can't compile SEMVER_REGEX regex");
14 static ref NUMERIC_REGEX: Regex = Regex::new(r"[0-9]+").expect("Can't compile NUMERIC_REGEX regex");
15}
16
17#[derive(Debug, PartialEq, Eq, Clone, Hash)]
18struct SubToken {
19 s: String,
20 n: Option<i64>,
21}
22
23impl SubToken {
24 fn new(s: &str) -> Self {
25 let n = s.parse::<i64>().ok();
26 SubToken {
27 s: s.to_string(),
28 n,
29 }
30 }
31
32 fn custom_char_order(&self, c: char) -> u8 {
33 match c {
34 '_' => 0,
35 'a'..='z' => 1 + (c as u8 - b'a'),
36 'A'..='Z' => 27 + (c as u8 - b'A'),
37 '0'..='9' => 53 + (c as u8 - b'0'),
38 _ => 255, }
40 }
41 fn compare_subtokens(&self, a: &str, b: &str) -> Ordering {
42 a.chars()
43 .zip(b.chars())
44 .map(|(ac, bc)| self.custom_char_order(ac).cmp(&self.custom_char_order(bc)))
45 .find(|&ordering| ordering != Ordering::Equal)
46 .unwrap_or_else(|| a.len().cmp(&b.len()))
47 }
48}
49
50#[test]
51fn test_subtoken_new() {
52 let a = SubToken::new("1");
53 assert_eq!(a.s, "1");
54 assert_eq!(a.n, Some(1));
55 let a = SubToken::new("a");
56 assert_eq!(a.s, "a");
57 assert_eq!(a.n, None);
58 let a = SubToken::new("a1");
59 assert_eq!(a.s, "a1");
60}
61
62#[test]
63fn test_subtoken_numeric_ordering() {
64 assert!(SubToken::new("2") < SubToken::new("10"));
66 assert!(SubToken::new("9") < SubToken::new("100"));
67 assert!(SubToken::new("122") > SubToken::new("34"));
68 assert!(SubToken::new("01") < SubToken::new("1"));
70 assert_eq!(SubToken::new("2").cmp(&SubToken::new("10")), Ordering::Less);
72 assert_eq!(
73 SubToken::new("2").partial_cmp(&SubToken::new("10")),
74 Some(Ordering::Less)
75 );
76}
77
78#[test]
79fn test_version_numeric_ordering() {
80 let a: RerVersion = "2.34.1".try_into().unwrap();
82 let b: RerVersion = "2.122.2".try_into().unwrap();
83 assert!(a < b, "2.34.1 should sort below 2.122.2");
84 let a: RerVersion = "1.9.0".try_into().unwrap();
85 let b: RerVersion = "1.10.0".try_into().unwrap();
86 assert!(a < b, "1.9.0 should sort below 1.10.0");
87}
88
89impl Ord for SubToken {
90 fn cmp(&self, other: &Self) -> Ordering {
91 match (self.n, other.n) {
93 (None, None) => self.compare_subtokens(&self.s, &other.s),
95 (None, Some(_)) => Ordering::Less,
97 (Some(_), None) => Ordering::Greater,
98 (Some(a), Some(b)) => a
101 .cmp(&b)
102 .then_with(|| self.compare_subtokens(&self.s, &other.s)),
103 }
104 }
105}
106
107impl PartialOrd for SubToken {
108 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
109 Some(self.cmp(other))
110 }
111}
112
113impl fmt::Display for SubToken {
114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115 write!(f, "{}", self.s)
116 }
117}
118
119#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
120struct AlphanumericVersionToken {
121 subtokens: Vec<SubToken>,
122}
123
124impl AlphanumericVersionToken {
125 fn new(token: &str) -> Result<Self, &'static str> {
126 if !ALPHABET_REGEX.is_match(token) {
127 Err("Invalid version token")
128 } else {
129 Ok(Self {
130 subtokens: Self::parse(token),
131 })
132 }
133 }
134 #[allow(dead_code)] fn create_random_token_string() -> Result<Self, &'static str> {
137 let s: String = rand::thread_rng()
138 .sample_iter(&Alphanumeric)
139 .take(7)
140 .map(char::from)
141 .collect();
142 Self::new(&s)
143 }
144
145 fn parse(s: &str) -> Vec<SubToken> {
146 let mut subtokens = Vec::new();
147 let mut alphas = NUMERIC_REGEX.split(s).peekable();
148 let mut numerics = NUMERIC_REGEX.find_iter(s).peekable();
149
150 while alphas.peek().is_some() || numerics.peek().is_some() {
151 if let Some(alpha) = alphas.next() {
152 if !alpha.is_empty() {
153 subtokens.push(SubToken::new(alpha));
154 }
155 }
156 if let Some(numeric) = numerics.next() {
157 subtokens.push(SubToken::new(numeric.as_str()));
158 }
159 }
160
161 subtokens
162 }
163}
164
165#[test]
166fn test_generate_random_version() {
167 AlphanumericVersionToken::create_random_token_string().unwrap();
168}
169
170impl fmt::Display for AlphanumericVersionToken {
171 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172 write!(
173 f,
174 "{}",
175 self.subtokens
176 .iter()
177 .map(ToString::to_string)
178 .collect::<String>()
179 )
180 }
181}
182
183impl AlphanumericVersionToken {
184 fn lowest() -> Self {
185 AlphanumericVersionToken {
186 subtokens: vec![SubToken::new("_")],
187 }
188 }
189 fn bump(&self) -> Self {
190 let mut next_subtokens = self.subtokens.clone();
191 let last = next_subtokens
192 .pop()
193 .expect("Token should have at least one subtoken");
194 if last.n.is_some() {
195 next_subtokens.push(last);
196 next_subtokens.push(SubToken::new("_"));
197 } else {
198 let new_last = SubToken::new(&(last.s + "_"));
199 next_subtokens.push(new_last);
200 }
201 AlphanumericVersionToken {
202 subtokens: next_subtokens,
203 }
204 }
205}
206#[allow(dead_code)] impl AlphanumericVersionToken {
208 pub fn compare(&self, other: &Self) -> Ordering {
209 self.subtokens.iter().cmp(other.subtokens.iter())
210 }
211}
212
213#[test]
214fn test_bump_alpha_num() {
215 let a = AlphanumericVersionToken::new("1").unwrap();
216 assert_eq!(a.subtokens[0].n, Some(1));
217 let b = AlphanumericVersionToken::new("1_").unwrap();
218 assert_eq!(b.subtokens[0].n, Some(1));
219 assert_eq!(b.subtokens[1].s, "_");
220 assert_eq!(b.subtokens[1].n, None);
221 assert_eq!(a.bump(), b);
222}
223#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
241pub struct RerVersion(Rc<RerVersionInner>);
242
243#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
245struct RerVersionInner {
246 tokens: Vec<AlphanumericVersionToken>,
247 seps: Vec<char>,
248}
249impl RerVersion {
250 fn parse_from_string(s: &str) -> Result<Self, &'static str> {
263 if !ALPHABET_REGEX.is_match(s) {
264 Err("Invalid version token")
265 } else {
266 let mut tokens = Vec::new();
267 let mut seps: Vec<char> = Vec::new();
268 let toks = ALPHABET_REGEX.find_iter(s);
269 let mut seps_iter = ALPHABET_REGEX.split(s);
270 for tok in toks {
271 tokens.push(AlphanumericVersionToken::new(tok.as_str())?);
272 if let Some(sep) = seps_iter.next() {
273 if let Some(c) = sep.chars().next() {
274 seps.push(c)
275 }
276 }
277 }
278 Ok(RerVersion(Rc::new(RerVersionInner { tokens, seps })))
279 }
280 }
281}
282impl RerVersion {
283 pub fn lowest() -> Self {
284 RerVersion(Rc::new(RerVersionInner {
285 tokens: vec![AlphanumericVersionToken::lowest()],
286 seps: vec![],
287 }))
288 }
289 pub fn bump(&self) -> Self {
290 let mut next_tokens = self.0.tokens.clone();
291 let last = next_tokens
292 .pop()
293 .expect("Token should have at least one subtoken");
294 next_tokens.push(last.bump());
295 RerVersion(Rc::new(RerVersionInner {
296 tokens: next_tokens,
297 seps: self.0.seps.clone(),
298 }))
299 }
300}
301impl fmt::Display for RerVersion {
302 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
303 let mut s = String::new();
304 for (i, token) in self.0.tokens.iter().enumerate() {
305 s.push_str(&token.to_string());
306 if i < self.0.seps.len() {
307 s.push(self.0.seps[i]);
308 }
309 }
310 write!(f, "{}", s)
311 }
312}
313
314impl TryFrom<&str> for RerVersion {
315 type Error = &'static str;
316 fn try_from(s: &str) -> Result<Self, Self::Error> {
317 RerVersion::parse_from_string(s)
318 }
319}
320impl TryFrom<String> for RerVersion {
321 type Error = &'static str;
322 fn try_from(s: String) -> Result<Self, Self::Error> {
323 RerVersion::parse_from_string(&s)
324 }
325}
326
327#[test]
328fn test_from() {
329 let v: RerVersion = "1.2.3-alpha+beta".try_into().unwrap();
330 assert_eq!(v.to_string(), "1.2.3-alpha+beta");
331}
332#[test]
333fn test_display() {
334 let v = RerVersion::parse_from_string("1.2.3-alpha+beta").unwrap();
335 assert_eq!(v.to_string(), "1.2.3-alpha+beta");
336}
337#[test]
338fn test_from_str() {
339 let v = RerVersion::parse_from_string("1.2.3").unwrap();
340 assert_eq!(v.0.tokens.len(), 3);
341 assert_eq!(v.0.seps.len(), 2);
342 let v = RerVersion::parse_from_string("1.2.3-alpha").unwrap();
343 assert_eq!(v.0.tokens.len(), 4);
344 assert_eq!(v.0.seps.len(), 3);
345 let v = RerVersion::parse_from_string("1.2.3-alpha+beta").unwrap();
346 assert_eq!(v.0.tokens.len(), 5);
347 assert_eq!(v.0.seps.len(), 4);
348 let v: RerVersion = "2.0.0_".try_into().unwrap();
349 assert_eq!(v.0.tokens[2], AlphanumericVersionToken::new("0_").unwrap());
350 assert_eq!(v.0.seps, vec!['.', '.']);
351}
352#[test]
353fn test_order() {
354 let a = RerVersion::parse_from_string("1.2.3").unwrap();
355 let b = RerVersion::parse_from_string("1.2.4").unwrap();
356 assert!(a < b);
357 let a = RerVersion::parse_from_string("1.2.3").unwrap();
358 let b = RerVersion::parse_from_string("1.2.3-alpha").unwrap();
359 assert!(a < b);
360 let a = RerVersion::parse_from_string("2.0.0").unwrap();
361 let b = RerVersion::parse_from_string("2.0.0_").unwrap();
362 assert!(a < b);
363}
364
365#[test]
366fn test_bump_rez_version() {
367 let a: RerVersion = "1.2.3".try_into().unwrap();
368 let b: RerVersion = "1.2.3_".try_into().unwrap();
369 assert_eq!(a.bump(), b);
370}
371
372#[test]
373fn test_compare_subtoken() {
374 let a = SubToken::new("1");
375 let b = SubToken::new("2");
376 assert!(a < b);
377 let a = SubToken::new("1");
378 let b = SubToken::new("1");
379 assert!(a == b);
380 let a = SubToken::new("1");
381 let b = SubToken::new("1a");
382 assert!(a >= b);
383 let a = SubToken::new("a");
384 let b = SubToken::new("1");
385 assert!(a < b);
386 let a = SubToken::new("a");
387 let b = SubToken::new("a");
388 assert!(a == b);
389 let a = SubToken::new("a");
390 let b = SubToken::new("A");
391 assert!(a < b);
392}
393#[test]
394fn test_alphanumeric_version_token_compare() {
395 let a = AlphanumericVersionToken::new("3").unwrap();
396 let b = AlphanumericVersionToken::new("4").unwrap();
397 assert!(a < b);
398 let a = AlphanumericVersionToken::new("01").unwrap();
399 let b = AlphanumericVersionToken::new("1").unwrap();
400 assert!(a < b);
401 let a = AlphanumericVersionToken::new("beta").unwrap();
402 let b = AlphanumericVersionToken::new("1").unwrap();
403 assert!(a < b);
404 let a = AlphanumericVersionToken::new("a").unwrap();
405 let b = AlphanumericVersionToken::new("A").unwrap();
406 assert!(a < b);
407 let a = AlphanumericVersionToken::new("alpha3").unwrap();
408 let b = AlphanumericVersionToken::new("alpha4").unwrap();
409 assert!(a < b);
410 let a = AlphanumericVersionToken::new("alpha").unwrap();
411 let b = AlphanumericVersionToken::new("alpha3").unwrap();
412 assert!(a < b);
413 let a = AlphanumericVersionToken::new("gamma33").unwrap();
414 let b = AlphanumericVersionToken::new("33gamma").unwrap();
415 assert!(a < b);
416}