1use crate::VersionInfo;
4
5pub struct VersionUtils;
7
8impl VersionUtils {
9 pub fn sort_versions_desc(mut versions: Vec<VersionInfo>) -> Vec<VersionInfo> {
11 versions.sort_by(|a, b| {
12 match (
14 Self::parse_semantic_version(&a.version),
15 Self::parse_semantic_version(&b.version),
16 ) {
17 (Ok(va), Ok(vb)) => vb.cmp(&va), _ => b.version.cmp(&a.version), }
20 });
21 versions
22 }
23
24 pub fn filter_stable_only(versions: Vec<VersionInfo>) -> Vec<VersionInfo> {
26 versions.into_iter().filter(|v| !v.prerelease).collect()
27 }
28
29 pub fn take_latest(versions: Vec<VersionInfo>, count: usize) -> Vec<VersionInfo> {
31 versions.into_iter().take(count).collect()
32 }
33
34 pub fn filter_lts_only(versions: Vec<VersionInfo>) -> Vec<VersionInfo> {
36 versions
37 .into_iter()
38 .filter(|v| {
39 v.metadata
40 .get("lts")
41 .map(|val| val == "true")
42 .unwrap_or(false)
43 })
44 .collect()
45 }
46
47 pub fn find_version<'a>(versions: &'a [VersionInfo], version: &str) -> Option<&'a VersionInfo> {
49 versions.iter().find(|v| v.version == version)
50 }
51
52 pub fn matches_pattern(version: &str, pattern: &str) -> bool {
54 match pattern {
55 "latest" => true,
56 "stable" => !Self::is_prerelease(version),
57 "lts" => false, _ => version == pattern,
59 }
60 }
61
62 pub fn is_prerelease(version: &str) -> bool {
64 version.contains("alpha")
65 || version.contains("beta")
66 || version.contains("rc")
67 || version.contains("pre")
68 || version.contains("dev")
69 || version.contains("snapshot")
70 }
71
72 pub fn clean_version(version: &str, prefixes: &[&str]) -> String {
74 let mut cleaned = version.to_string();
75 for prefix in prefixes {
76 if cleaned.starts_with(prefix) {
77 cleaned = cleaned[prefix.len()..].to_string();
78 break;
79 }
80 }
81 cleaned
82 }
83
84 fn parse_semantic_version(version: &str) -> Result<(u32, u32, u32, String), String> {
86 let clean_version = version.trim_start_matches('v');
87 let parts: Vec<&str> = clean_version.split('.').collect();
88
89 if parts.len() < 2 {
90 return Err(format!("Invalid version format: {}", version));
91 }
92
93 let major = parts[0]
94 .parse::<u32>()
95 .map_err(|_| format!("Invalid major version: {}", parts[0]))?;
96
97 let minor = parts[1]
98 .parse::<u32>()
99 .map_err(|_| format!("Invalid minor version: {}", parts[1]))?;
100
101 let (patch, suffix) = if parts.len() > 2 {
102 let patch_part = parts[2];
103 if let Some(dash_pos) = patch_part.find('-') {
104 let patch_num = patch_part[..dash_pos]
105 .parse::<u32>()
106 .map_err(|_| format!("Invalid patch version: {}", &patch_part[..dash_pos]))?;
107 let suffix = patch_part[dash_pos..].to_string();
108 (patch_num, suffix)
109 } else {
110 let patch_num = patch_part
111 .parse::<u32>()
112 .map_err(|_| format!("Invalid patch version: {}", patch_part))?;
113 (patch_num, String::new())
114 }
115 } else {
116 (0, String::new())
117 };
118
119 Ok((major, minor, patch, suffix))
120 }
121
122 pub fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering {
124 match (
125 Self::parse_semantic_version(a),
126 Self::parse_semantic_version(b),
127 ) {
128 (Ok(va), Ok(vb)) => va.cmp(&vb),
129 _ => a.cmp(b), }
131 }
132
133 pub fn is_greater_than(a: &str, b: &str) -> bool {
135 Self::compare_versions(a, b) == std::cmp::Ordering::Greater
136 }
137
138 pub fn is_less_than(a: &str, b: &str) -> bool {
140 Self::compare_versions(a, b) == std::cmp::Ordering::Less
141 }
142
143 pub fn is_equal(a: &str, b: &str) -> bool {
145 Self::compare_versions(a, b) == std::cmp::Ordering::Equal
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_version_utils() {
155 assert!(VersionUtils::is_prerelease("1.0.0-alpha"));
156 assert!(VersionUtils::is_prerelease("2.0.0-beta.1"));
157 assert!(VersionUtils::is_prerelease("3.0.0-rc.1"));
158 assert!(!VersionUtils::is_prerelease("1.0.0"));
159
160 assert_eq!(VersionUtils::clean_version("v1.0.0", &["v"]), "1.0.0");
161 assert_eq!(VersionUtils::clean_version("go1.21.0", &["go"]), "1.21.0");
162 assert_eq!(VersionUtils::clean_version("1.0.0", &["v", "go"]), "1.0.0");
163 }
164
165 #[test]
166 fn test_version_comparison() {
167 assert!(VersionUtils::is_greater_than("1.2.3", "1.2.2"));
168 assert!(VersionUtils::is_less_than("1.2.2", "1.2.3"));
169 assert!(VersionUtils::is_equal("1.2.3", "1.2.3"));
170
171 assert!(VersionUtils::is_greater_than("2.0.0", "1.9.9"));
172 assert!(VersionUtils::is_greater_than("1.3.0", "1.2.9"));
173 }
174
175 #[test]
176 fn test_matches_pattern() {
177 assert!(VersionUtils::matches_pattern("1.2.3", "latest"));
178 assert!(VersionUtils::matches_pattern("1.2.3", "stable"));
179 assert!(!VersionUtils::matches_pattern("1.2.3-alpha", "stable"));
180 assert!(VersionUtils::matches_pattern("1.2.3", "1.2.3"));
181 assert!(!VersionUtils::matches_pattern("1.2.3", "1.2.4"));
182 }
183
184 #[test]
185 fn test_filter_stable_only() {
186 let versions = vec![
187 VersionInfo::new("1.0.0".to_string()),
188 VersionInfo::new("1.1.0-alpha".to_string()).as_prerelease(),
189 VersionInfo::new("1.1.0".to_string()),
190 ];
191
192 let stable = VersionUtils::filter_stable_only(versions);
193 assert_eq!(stable.len(), 2);
194 assert_eq!(stable[0].version, "1.0.0");
195 assert_eq!(stable[1].version, "1.1.0");
196 }
197}