1use std::cmp::Ordering;
6
7pub fn compare_versions(a: &str, b: &str) -> Ordering {
9 let a_parts: Vec<u64> = a
10 .split('.')
11 .filter_map(|p| p.split(|c: char| !c.is_ascii_digit()).next())
12 .filter_map(|p| p.parse().ok())
13 .collect();
14 let b_parts: Vec<u64> = b
15 .split('.')
16 .filter_map(|p| p.split(|c: char| !c.is_ascii_digit()).next())
17 .filter_map(|p| p.parse().ok())
18 .collect();
19
20 let max_len = a_parts.len().max(b_parts.len());
21 for i in 0..max_len {
22 let ap = a_parts.get(i).copied().unwrap_or(0);
23 let bp = b_parts.get(i).copied().unwrap_or(0);
24 match ap.cmp(&bp) {
25 Ordering::Equal => {}
26 other => return other,
27 }
28 }
29 Ordering::Equal
30}
31
32pub fn version_matches_requirement(version: &str, requirement: &str) -> bool {
34 let req = requirement.trim();
35
36 if req == "*" {
37 return true;
38 }
39
40 if req.starts_with("~>") {
42 let req_ver = req.trim_start_matches("~>").trim();
43 return matches_pessimistic(version, req_ver);
44 }
45
46 if req.starts_with(">=") {
48 let req_ver = req.trim_start_matches(">=").trim();
49 return compare_versions(version, req_ver) != Ordering::Less;
50 }
51
52 if req.starts_with('>') && !req.starts_with(">=") {
54 let req_ver = req.trim_start_matches('>').trim();
55 return compare_versions(version, req_ver) == Ordering::Greater;
56 }
57
58 if req.starts_with("<=") {
60 let req_ver = req.trim_start_matches("<=").trim();
61 return compare_versions(version, req_ver) != Ordering::Greater;
62 }
63
64 if req.starts_with('<') && !req.starts_with("<=") {
66 let req_ver = req.trim_start_matches('<').trim();
67 return compare_versions(version, req_ver) == Ordering::Less;
68 }
69
70 if req.starts_with("!=") {
72 let req_ver = req.trim_start_matches("!=").trim();
73 return version != req_ver;
74 }
75
76 if let Some(req_ver) = req.strip_prefix('=') {
78 return version == req_ver.trim();
79 }
80
81 version == req || version.starts_with(&format!("{req}."))
83}
84
85fn matches_pessimistic(version: &str, requirement: &str) -> bool {
87 let req_parts: Vec<&str> = requirement.split('.').collect();
88 let ver_parts: Vec<&str> = version.split('.').collect();
89
90 if ver_parts.len() < req_parts.len() {
91 return false;
92 }
93
94 for i in 0..(req_parts.len().saturating_sub(1)) {
96 let req_part = req_parts
97 .get(i)
98 .and_then(|p| p.split(|c: char| !c.is_ascii_digit()).next());
99 let ver_part = ver_parts
100 .get(i)
101 .and_then(|p| p.split(|c: char| !c.is_ascii_digit()).next());
102 if req_part != ver_part {
103 return false;
104 }
105 }
106
107 let last_idx = req_parts.len() - 1;
109 let req_last: u64 = req_parts[last_idx]
110 .split(|c: char| !c.is_ascii_digit())
111 .next()
112 .and_then(|p| p.parse().ok())
113 .unwrap_or(0);
114 let ver_last: u64 = ver_parts
115 .get(last_idx)
116 .and_then(|v| v.split(|c: char| !c.is_ascii_digit()).next())
117 .and_then(|p| p.parse().ok())
118 .unwrap_or(0);
119
120 ver_last >= req_last
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_compare_versions() {
129 assert_eq!(compare_versions("1.0.0", "1.0.0"), Ordering::Equal);
130 assert_eq!(compare_versions("1.0.1", "1.0.0"), Ordering::Greater);
131 assert_eq!(compare_versions("1.0.0", "1.0.1"), Ordering::Less);
132 assert_eq!(compare_versions("2.0.0", "1.9.9"), Ordering::Greater);
133 assert_eq!(compare_versions("1.0.0", "1.0"), Ordering::Equal);
134 assert_eq!(compare_versions("1.0", "1.0.0"), Ordering::Equal);
135 }
136
137 #[test]
138 fn test_matches_pessimistic() {
139 assert!(matches_pessimistic("1.0.5", "1.0"));
141 assert!(matches_pessimistic("1.0.0", "1.0"));
142 assert!(matches_pessimistic("1.9.9", "1.0"));
143 assert!(!matches_pessimistic("2.0.0", "1.0"));
144
145 assert!(matches_pessimistic("1.0.5", "1.0.5"));
147 assert!(matches_pessimistic("1.0.9", "1.0.5"));
148 assert!(!matches_pessimistic("1.1.0", "1.0.5"));
149 assert!(!matches_pessimistic("1.0.4", "1.0.5"));
150 }
151
152 #[test]
153 fn test_version_matches_requirement() {
154 assert!(version_matches_requirement("7.0.8", "~> 7.0"));
156 assert!(version_matches_requirement("7.0.0", "~> 7.0"));
157 assert!(!version_matches_requirement("8.0.0", "~> 7.0"));
158
159 assert!(version_matches_requirement("1.5.0", ">= 1.1"));
161 assert!(version_matches_requirement("1.1.0", ">= 1.1"));
162 assert!(!version_matches_requirement("1.0.0", ">= 1.1"));
163
164 assert!(version_matches_requirement("2.0.0", "> 1.0"));
166 assert!(!version_matches_requirement("1.0.0", "> 1.0"));
167
168 assert!(version_matches_requirement("1.0.0", "<= 1.0"));
170 assert!(!version_matches_requirement("1.1.0", "<= 1.0"));
171
172 assert!(version_matches_requirement("0.9.0", "< 1.0"));
174 assert!(!version_matches_requirement("1.0.0", "< 1.0"));
175
176 assert!(version_matches_requirement("1.0.0", "= 1.0.0"));
178 assert!(!version_matches_requirement("1.0.1", "= 1.0.0"));
179
180 assert!(version_matches_requirement("1.0.1", "!= 1.0.0"));
182 assert!(!version_matches_requirement("1.0.0", "!= 1.0.0"));
183
184 assert!(version_matches_requirement("1.0.0", "*"));
186 assert!(version_matches_requirement("0.0.1", "*"));
187 assert!(version_matches_requirement("99.99.99", "*"));
188 }
189}