1use crate::SortedSlice;
6use regex::Regex;
7use std::{cmp::Reverse, sync::LazyLock};
8
9static STABLE: LazyLock<Regex> = LazyLock::new(|| {
11 Regex::new(
12 r"(polkadot-(parachain-)?)?stable(?P<year>\d{2})(?P<month>\d{2})(-(?P<patch>\d+))?(-rc\d+)?",
13 )
14 .expect("Valid regex")
15});
16static VERSION: LazyLock<Regex> = LazyLock::new(|| {
18 Regex::new(r"v(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-rc\d+)?").expect("Valid regex")
19});
20
21pub type Version = (u32, u32, u32);
23
24pub fn parse_latest_tag(tags: &[impl AsRef<str>]) -> Option<&str> {
30 match parse_latest_stable_tag(tags) {
31 Some(last_stable_tag) => Some(last_stable_tag),
32 None => parse_latest_semantic_version(tags),
33 }
34}
35
36fn parse_latest_stable_tag(tags: &[impl AsRef<str>]) -> Option<&str> {
38 tags.iter()
39 .filter_map(|tag| parse_stable_version(tag.as_ref()).map(|version| (tag, version)))
40 .max_by(|a, b| {
41 let (_, (year_a, month_a, patch_a)) = a;
42 let (_, (year_b, month_b, patch_b)) = b;
43 year_a
45 .cmp(year_b)
46 .then_with(|| month_a.cmp(month_b))
47 .then_with(|| patch_a.cmp(patch_b))
48 })
49 .map(|(tag, _)| tag.as_ref())
50}
51
52pub fn parse_latest_semantic_version(items: &[impl AsRef<str>]) -> Option<&str> {
58 items
59 .iter()
60 .filter_map(|tag| parse_semantic_version(tag.as_ref()).map(|version| (tag, version)))
61 .max_by_key(|&(_, version)| version)
62 .map(|(tag, _)| tag.as_ref())
63}
64
65pub fn parse_semantic_version(value: impl AsRef<str>) -> Option<Version> {
70 let value = value.as_ref();
72 if value.contains("-rc") {
73 return None;
74 }
75 VERSION.captures(value).and_then(|v| {
76 let major = v.name("major")?.as_str().parse::<u32>().ok()?;
77 let minor = v.name("minor")?.as_str().parse::<u32>().ok()?;
78 let patch = v.name("patch")?.as_str().parse::<u32>().ok()?;
79 Some((major, minor, patch))
80 })
81}
82
83pub fn parse_stable_version(value: &str) -> Option<Version> {
88 if value.contains("-rc") {
90 return None;
91 }
92 STABLE.captures(value).and_then(|v| {
93 let year = v.name("year")?.as_str().parse::<u32>().ok()?;
94 let month = v.name("month")?.as_str().parse::<u32>().ok()?;
95 let patch = v.name("patch").and_then(|m| m.as_str().parse::<u32>().ok()).unwrap_or(0);
96 Some((year, month, patch))
97 })
98}
99
100pub fn parse_version(value: &str) -> Option<Version> {
106 match parse_stable_version(value) {
107 Some(stable_version) => Some(stable_version),
108 None => parse_semantic_version(value),
109 }
110}
111
112pub fn sort_by_latest_semantic_version<T: AsRef<str>>(versions: &mut [T]) -> SortedSlice<T> {
118 SortedSlice::by_key(versions, |tag| {
119 parse_semantic_version(tag.as_ref())
120 .map(|version| Reverse(Some(version)))
121 .unwrap_or(Reverse(None))
122 })
123}
124
125pub fn sort_by_latest_stable_version<T: AsRef<str>>(versions: &mut [T]) -> SortedSlice<T> {
131 SortedSlice::by_key(versions, |tag| {
132 parse_stable_version(tag.as_ref())
133 .map(|version| Reverse(Some(version)))
134 .unwrap_or(Reverse(None))
135 })
136}
137
138pub fn sort_by_latest_version<T: AsRef<str>>(versions: &mut [T]) -> SortedSlice<T> {
144 SortedSlice::by_key(versions, |tag| {
145 parse_version(tag.as_ref())
146 .map(|version| Reverse(Some(version)))
147 .unwrap_or(Reverse(None))
148 })
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn parse_latest_tag_works() {
157 let mut tags = vec![];
158 assert_eq!(parse_latest_tag(&tags), None);
159 tags = vec![
160 "polkadot-stable2409",
161 "polkadot-stable2409-1",
162 "polkadot-stable2407",
163 "polkadot-v1.10.0",
164 "polkadot-v1.11.0",
165 "polkadot-v1.12.0",
166 "polkadot-v1.7.0",
167 "polkadot-v1.8.0",
168 "polkadot-v1.9.0",
169 "v1.15.1-rc2",
170 ];
171 assert_eq!(parse_latest_tag(&tags), Some("polkadot-stable2409-1"));
172 }
173
174 #[test]
175 fn parse_stable_format_works() {
176 let mut tags = vec![];
177 assert_eq!(parse_latest_stable_tag(&tags), None);
178 tags = vec!["polkadot-stable2407", "polkadot-stable2408"];
179 assert_eq!(parse_latest_stable_tag(&tags), Some("polkadot-stable2408"));
180 tags = vec!["polkadot-stable2407", "polkadot-stable2501"];
181 assert_eq!(parse_latest_stable_tag(&tags), Some("polkadot-stable2501"));
182 tags = vec!["polkadot-stable2407", "polkadot-stable2407-1", "polkadot-stable2407-1-rc1"];
184 assert_eq!(parse_latest_stable_tag(&tags), Some("polkadot-stable2407-1"));
185 }
186
187 #[test]
188 fn parse_latest_semantic_version_works() {
189 let mut tags: Vec<&str> = vec![];
190 assert_eq!(parse_latest_semantic_version(&tags), None);
191 tags = vec![
192 "polkadot-v1.10.0",
193 "polkadot-v1.11.0",
194 "polkadot-v1.12.0",
195 "polkadot-v1.7.0",
196 "polkadot-v1.8.0",
197 "polkadot-v1.9.0",
198 ];
199 assert_eq!(parse_latest_semantic_version(&tags), Some("polkadot-v1.12.0"));
200 tags = vec!["v1.0.0", "v2.0.0", "v3.0.0"];
201 assert_eq!(parse_latest_semantic_version(&tags), Some("v3.0.0"));
202 tags = vec!["polkadot-v1.12.0", "v1.15.1-rc2"];
204 assert_eq!(parse_latest_semantic_version(&tags), Some("polkadot-v1.12.0"));
205 }
206
207 #[test]
208 fn parse_version_works() {
209 for (tag, expected) in [
210 ("polkadot-stable2409", Some((24, 9, 0))),
211 ("polkadot-stable2409-1", Some((24, 9, 1))),
212 ("polkadot-v1.18.0", Some((1, 18, 0))),
213 ("polkadot-v1.18.1", Some((1, 18, 1))),
214 ("v1.15.1", Some((1, 15, 1))),
215 ("v1.15.1-rc2", None),
216 ] {
217 assert_eq!(parse_version(tag), expected);
218 }
219 }
220
221 #[test]
222 fn sort_by_latest_semantic_version_works() {
223 assert_eq!(
224 sort_by_latest_semantic_version(
225 [
226 "polkadot-v1.10.0",
227 "polkadot-v1.11.0",
228 "v1.17.0",
229 "polkadot-v1.12.0",
230 "polkadot-v1.7.0",
231 "v1.18.0",
232 "polkadot-v1.8.0",
233 "polkadot-v1.9.0",
234 "v1.18.1",
235 ]
236 .as_mut_slice()
237 )
238 .0,
239 [
240 "v1.18.1",
241 "v1.18.0",
242 "v1.17.0",
243 "polkadot-v1.12.0",
244 "polkadot-v1.11.0",
245 "polkadot-v1.10.0",
246 "polkadot-v1.9.0",
247 "polkadot-v1.8.0",
248 "polkadot-v1.7.0",
249 ]
250 );
251 }
252
253 #[test]
254 fn sort_by_latest_stable_version_works() {
255 assert_eq!(
256 sort_by_latest_stable_version(
257 [
258 "polkadot-stable2409",
259 "polkadot-stable2409-1",
260 "polkadot-stable2407",
261 "polkadot-stable2503",
262 "polkadot-stable2503-1"
263 ]
264 .as_mut_slice()
265 )
266 .0,
267 [
268 "polkadot-stable2503-1",
269 "polkadot-stable2503",
270 "polkadot-stable2409-1",
271 "polkadot-stable2409",
272 "polkadot-stable2407",
273 ]
274 );
275 }
276
277 #[test]
278 fn sort_by_latest_version_works() {
279 assert_eq!(
280 sort_by_latest_version(
281 [
282 "polkadot-v1.10.0",
283 "polkadot-v1.11.0",
284 "v1.17.0",
285 "polkadot-v1.12.0",
286 "polkadot-v1.7.0",
287 "v1.18.0",
288 "polkadot-v1.8.0",
289 "polkadot-v1.9.0",
290 "v1.18.1",
291 "polkadot-stable2409",
292 "polkadot-stable2409-1",
293 "polkadot-stable2407",
294 "polkadot-stable2503",
295 "polkadot-stable2503-1"
296 ]
297 .as_mut_slice()
298 )
299 .0,
300 [
301 "polkadot-stable2503-1",
302 "polkadot-stable2503",
303 "polkadot-stable2409-1",
304 "polkadot-stable2409",
305 "polkadot-stable2407",
306 "v1.18.1",
307 "v1.18.0",
308 "v1.17.0",
309 "polkadot-v1.12.0",
310 "polkadot-v1.11.0",
311 "polkadot-v1.10.0",
312 "polkadot-v1.9.0",
313 "polkadot-v1.8.0",
314 "polkadot-v1.7.0",
315 ]
316 );
317 }
318}